diff --git a/apps/docs/scripts/auth-cli.ts b/apps/docs/scripts/auth-cli.ts index 046988a..e06d461 100644 --- a/apps/docs/scripts/auth-cli.ts +++ b/apps/docs/scripts/auth-cli.ts @@ -187,17 +187,18 @@ async function main() { assert.ok(searchIndex.some((doc: { slug: string; searchText: string }) => doc.slug === "source-studies/pretext/measurement-cache" && doc.searchText.includes("getSegmentMetrics"))); for (const doc of searchIndex as { slug: string; sourceSlug: string }[]) { assert.ok(!doc.slug.split("/").some((part) => /^\d+-/.test(part)), doc.slug); - const legacy = await request(`/docs/${doc.sourceSlug}`, { headers }); + const legacy = await request(`/docs/${doc.sourceSlug}?from=legacy`, { headers }); assert.equal(legacy.status, 308, doc.sourceSlug); - assert.equal(new URL(legacy.headers.get("location")!, base).pathname, `/docs/${doc.slug}`); + const destination = new URL(legacy.headers.get("location")!, base); + assert.equal(destination.pathname, `/docs/${doc.slug}`); + assert.equal(destination.searchParams.get("from"), "legacy"); + assert.ok(legacy.headers.get("cache-control")?.includes("no-store")); } let home = ""; let purpose = ""; for (const route of routes) { const response = await request(route, { headers }); - const alias = (searchIndex as { sourceSlug: string }[]).some((doc) => route === `/docs/${doc.sourceSlug}`); - assert.equal(response.status, alias ? 308 : 200, route); - if (alias) continue; + assert.equal(response.status, 200, route); const html = await response.text(); if (route === "/") home = html; if (route === "/docs/endor/endor-purpose-draft") purpose = html; diff --git a/apps/docs/src/pages/docs/[...slug].tsx b/apps/docs/src/pages/docs/[...slug].tsx index a4d7415..5ddb476 100644 --- a/apps/docs/src/pages/docs/[...slug].tsx +++ b/apps/docs/src/pages/docs/[...slug].tsx @@ -25,16 +25,14 @@ import { headingsFor } from "@/lib/navigation"; type Props = { doc: Doc; docs: DocLink[] }; export const getStaticPaths: GetStaticPaths = async () => ({ - paths: (await listDocs()).flatMap(({ slug, sourceSlug }) => [...new Set([slug, sourceSlug])].map((value) => ({ params: { slug: value.split("/") } }))), + paths: (await listDocs()).map(({ slug }) => ({ params: { slug: slug.split("/") } })), fallback: false, }); export const getStaticProps: GetStaticProps = async ({ params }) => { const slug = Array.isArray(params?.slug) ? params.slug.join("/") : params?.slug; if (typeof slug !== "string") return { notFound: true }; const docs = await listDocs(); - const match = docs.find((d) => d.slug === slug || d.sourceSlug === slug); - if (!match) return { notFound: true }; - if (slug !== match.slug) return { redirect: { destination: `/docs/${match.slug}`, permanent: true } }; + if (!docs.some((d) => d.slug === slug)) return { notFound: true }; return { props: { doc: await readDoc(slug), docs } }; }; function textOf(value: ReactNode): string { diff --git a/apps/docs/src/proxy.ts b/apps/docs/src/proxy.ts index 7718d77..021e478 100644 --- a/apps/docs/src/proxy.ts +++ b/apps/docs/src/proxy.ts @@ -1,5 +1,10 @@ import { NextResponse, type NextRequest } from "next/server"; import { authConfig, SESSION_COOKIE, validSession } from "./lib/auth"; +import catalog from "../../../content/catalog.json"; + +const legacyRoutes = new Map(Object.entries(catalog.documents).map(([source, metadata]) => [ + `/docs/${source}`, `/docs/${metadata.slug}`, +])); export function proxy(request: NextRequest) { const path = request.nextUrl.pathname; @@ -49,6 +54,13 @@ export function proxy(request: NextRequest) { ); } } + // Keep legacy URLs behind authentication and out of static generation. + const destination = legacyRoutes.get(path); + if (destination && ["GET", "HEAD"].includes(request.method)) { + const target = request.nextUrl.clone(); + target.pathname = destination; + return NextResponse.redirect(target, { status: 308, headers }); + } return NextResponse.next({ headers }); } if (path.startsWith("/api/") || !["GET", "HEAD"].includes(request.method)) {