476173b6b5
* 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
67 lines
2.7 KiB
JavaScript
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);
|