Your Sitemap Will Drift, So Derive It
- seo
- nextjs
- architecture
A sitemap that lists pages you no longer have, and omits pages you just shipped, is worse than no sitemap. It teaches search engines to distrust the file. The usual cause is not carelessness — it is that the sitemap is a second copy of information that already exists in your routing.
Two copies always drift
Adding a page in the Next.js App Router means creating a folder. Nothing about that action touches your sitemap. So the sitemap is correct only for as long as everyone remembers a step that no tool enforces.
Give it a few months and a few contributors, and it will be wrong.
Declare routes once
Instead, write the routes down in one place and let everything else read from it:
// src/lib/seo/routes.ts
export const staticRoutes = [
{ path: "/", label: "Home", priority: 1.0, showInNav: false },
{ path: "/projects", label: "Projects", priority: 0.9, showInNav: true },
{ path: "/blog", label: "Writing", priority: 0.9, showInNav: true },
{ path: "/about", label: "About", priority: 0.8, showInNav: true },
] as const;The navigation maps over it. The footer maps over it. sitemap.ts maps over it
and appends the dynamic slugs. There is now exactly one copy of the answer to
"what pages does this site have?"
Then close the loop with a test
Deriving the sitemap removes most of the drift, but not all of it: someone can
still create app/(site)/speaking/page.tsx and never add it to the registry. A
test catches that:
test("every route folder is registered", () => {
const folders = readRouteFoldersFromDisk();
const registered = staticRoutes.map((r) => r.path);
expect(folders.sort()).toEqual(registered.sort());
});Now the filesystem and the registry cannot disagree without the build going red.
The general shape
This pattern is not really about sitemaps. It is: when the same fact is written in two places, a tool should either derive one from the other or assert they match. Anything you rely on a human to remember is a regression waiting for a busy week.