From bce56fd9bec81253a2bae4f72ef4392a7eb6f010 Mon Sep 17 00:00:00 2001 From: Mathias Oterhals Myklebust Date: Mon, 23 Sep 2024 11:04:02 +0200 Subject: [PATCH] refactor(app): extract sitemap base url parsing into common utility function --- src/app/env.ts | 12 ++++++++++++ src/app/robots.ts | 15 +++++++++++++-- src/app/sitemap.ts | 13 ++++++------- src/middleware.ts | 31 +++++++++++++++++++------------ 4 files changed, 50 insertions(+), 21 deletions(-) create mode 100644 src/app/env.ts diff --git a/src/app/env.ts b/src/app/env.ts new file mode 100644 index 000000000..15f0036c8 --- /dev/null +++ b/src/app/env.ts @@ -0,0 +1,12 @@ +import { Result, ResultError, ResultOk } from "studio/utils/result"; + +export function readBaseUrl(): Result { + const url = process.env.NEXT_PUBLIC_URL; + if (url === undefined) { + return ResultError("Missing environment variable: NEXT_PUBLIC_URL"); + } + if (!URL.canParse(url)) { + return ResultError("Invalid environment variable: NEXT_PUBLIC_URL"); + } + return ResultOk(new URL(url)); +} diff --git a/src/app/robots.ts b/src/app/robots.ts index 3265d2247..fece8039f 100644 --- a/src/app/robots.ts +++ b/src/app/robots.ts @@ -1,11 +1,22 @@ import type { MetadataRoute } from "next"; +import { readBaseUrl } from "./env"; + export default function robots(): MetadataRoute.Robots { - return { + const robotsFile: MetadataRoute.Robots = { rules: { userAgent: "*", disallow: ["/studio", "/shared", "/api"], }, - sitemap: new URL("sitemap.xml", process.env.NEXT_PUBLIC_URL).toString(), }; + const baseUrlResult = readBaseUrl(); + if (baseUrlResult.ok) { + robotsFile.sitemap = new URL("sitemap.xml", baseUrlResult.value).toString(); + } else { + console.warn( + "Could not include sitemap in robots.txt, missing base url:", + baseUrlResult.error, + ); + } + return robotsFile; } diff --git a/src/app/sitemap.ts b/src/app/sitemap.ts index 274a90660..8528b95f7 100644 --- a/src/app/sitemap.ts +++ b/src/app/sitemap.ts @@ -7,21 +7,20 @@ import { DOCUMENTS_WITH_SLUG_QUERY } from "studio/lib/queries/document"; import { LANDING_PAGE_QUERY } from "studio/lib/queries/navigation"; import { token } from "studio/lib/token"; +import { readBaseUrl } from "./env"; + const clientWithToken = client.withConfig({ token }); export const dynamic = "force-dynamic"; export const fetchCache = "default-no-store"; export default async function sitemap(): Promise { - const baseUrl = - process.env.NEXT_PUBLIC_URL !== undefined && - URL.canParse(process.env.NEXT_PUBLIC_URL) - ? new URL(process.env.NEXT_PUBLIC_URL) - : undefined; - if (baseUrl === undefined) { - console.error("Failed to generate sitemap, missing baseUrl"); + const baseUrlResult = readBaseUrl(); + if (!baseUrlResult.ok) { + console.error("Failed to generate sitemap:", baseUrlResult.error); return []; } + const baseUrl = baseUrlResult.value; const slugDocuments = await clientWithToken.fetch( DOCUMENTS_WITH_SLUG_QUERY, ); diff --git a/src/middleware.ts b/src/middleware.ts index d77ee7226..f639dc33c 100644 --- a/src/middleware.ts +++ b/src/middleware.ts @@ -3,28 +3,35 @@ import { NextRequest, NextResponse } from "next/server"; import { RedirectDestinationSlugPage } from "studio/lib/interfaces/redirect"; import { REDIRECT_BY_SOURCE_SLUG_QUERY } from "studio/lib/queries/redirects"; +import { readBaseUrl } from "./app/env"; import { HTTP_STATUSES } from "./utils/http"; export async function middleware(request: NextRequest) { + const baseUrlResult = readBaseUrl(); + if (!baseUrlResult.ok) { + console.error( + "Failed to run middleware, missing base url:", + baseUrlResult.error, + ); + return; + } + const baseUrl = baseUrlResult.value; const slug = request.nextUrl.pathname; const slugQueryParam = slug.replace(/^\/+/, ""); /* fetching redirect data via API route to avoid token leaking to client (middleware should run on server, but `experimental_taintUniqueValue` begs to differ...) */ - const redirectRes = await fetch( - new URL("/api/fetchData", process.env.NEXT_PUBLIC_URL), - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - query: REDIRECT_BY_SOURCE_SLUG_QUERY, - params: { slug: slugQueryParam }, - }), + const redirectRes = await fetch(new URL("/api/fetchData", baseUrl), { + method: "POST", + headers: { + "Content-Type": "application/json", }, - ); + body: JSON.stringify({ + query: REDIRECT_BY_SOURCE_SLUG_QUERY, + params: { slug: slugQueryParam }, + }), + }); if (redirectRes.ok) { const redirect: RedirectDestinationSlugPage | null = await redirectRes.json();