Files
vercel-labs--zero-native/docs/next.config.mjs
Chris Tate 476173b6b5 docs: add canonical machine-readable routes (#248)
* docs: add canonical machine-readable routes

- Move documentation under /docs with permanent legacy redirects and explicit canonical metadata.
- Serve synchronized .md siblings and llms.txt from the canonical MDX sources.
- Gate redirects, metadata, sitemap entries, and internal links against SEO regressions.

* fix(docs): harden canonical route migration

* fix(docs): preserve query strings in legacy redirects

* fix(docs): preserve MDX content in markdown routes

* fix(docs): decode MDX string expressions
2026-08-01 14:14:25 -05:00

67 lines
2.7 KiB
JavaScript

import createMDX from "@next/mdx";
import { createRequire } from "node:module";
import { readdirSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
// Resolve the plugin to an absolute path (still a string, so the config
// stays serializable for Turbopack). A bare "remark-gfm" is require()d
// from the MDX loader's own package context, which under pnpm's strict
// module isolation cannot see this app's dependencies — production
// builds resolved it, the Turbopack dev server did not.
const require = createRequire(import.meta.url);
const docsContentDir = fileURLToPath(new URL("./src/app/docs", import.meta.url));
function docsSlugs(dir = docsContentDir, segments = []) {
const slugs = [];
for (const entry of readdirSync(dir, { withFileTypes: true })) {
if (entry.isDirectory()) {
slugs.push(...docsSlugs(path.join(dir, entry.name), [...segments, entry.name]));
} else if (entry.name === "page.mdx" && segments.length > 0) {
slugs.push(segments.join("/"));
}
}
return slugs;
}
const withMDX = createMDX({
options: {
// GFM is what gives .mdx pages pipe tables (plus autolinks and
// strikethrough) — without it, table markdown renders as a plain
// paragraph of pipes.
remarkPlugins: [[require.resolve("remark-gfm")]],
},
});
/** @type {import('next').NextConfig} */
const nextConfig = {
pageExtensions: ["ts", "tsx", "md", "mdx"],
// CI-style builds set NEXT_DIST_DIR so `pnpm check` never shares .next
// with a running dev server (a shared dist dir corrupts the dev cache).
distDir: process.env.NEXT_DIST_DIR || ".next",
// The gate builds into .next-gate INSIDE this dir; without an ignore,
// the dev watcher sees every one of those build files land and
// recompiles continuously whenever a gate runs.
watchOptions: {
ignored: ["**/.next-gate/**", "**/.next-check/**"],
},
async redirects() {
// Config redirects preserve the request query string. Keeping these out
// of the prerendered catch-all route avoids baking a query-less Location
// header into every legacy URL's static response.
const legacyDocsRedirects = docsSlugs().flatMap((slug) => [
{ source: `/${slug}`, destination: `/docs/${slug}`, permanent: true },
{ source: `/${slug}.md`, destination: `/docs/${slug}.md`, permanent: true },
{ source: `/md/${slug}`, destination: `/docs/${slug}.md`, permanent: true },
]);
return [
// The Philosophy page became the Introduction, the opening page of the docs.
{ source: "/philosophy", destination: "/docs/introduction", permanent: true },
...legacyDocsRedirects,
];
},
};
export default withMDX(nextConfig);