sitemap.xml in Next.js: a production checklist
A bad sitemap quietly degrades your indexing for months. The most common failure modes are not syntax errors — they are subtle quality issues that cause search engines to deprioritize your sitemap. This is the checklist for shipping a sitemap that works.
The Next.js convention
In the App Router, prefer app/sitemap.ts over a static file. The function returns a typed array and Next handles the XML serialization, content-type header, and caching.
// app/sitemap.ts
import type { MetadataRoute } from "next";
const BASE_URL = "https://yourdomain.com";
const BUILD_DATE = new Date("2026-05-13T00:00:00Z");
export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: `${BASE_URL}/`,
lastModified: BUILD_DATE,
changeFrequency: "weekly",
priority: 1,
},
{
url: `${BASE_URL}/blog`,
lastModified: BUILD_DATE,
changeFrequency: "weekly",
priority: 0.9,
},
];
}That gives you /sitemap.xml for free, with the right content type and sitemap.org schema.
Mistakes that quietly hurt indexing
lastModified: new Date() on every URL
The most common bug. If you set lastModified to a fresh Date() per request, every URL appears modified on every fetch. Search engines respond by treating the entire sitemap as low-signal churn.
Fix: use a stable build date constant.
const BUILD_DATE = new Date("2026-05-13T00:00:00Z");For per-page accuracy, store a real updatedAt per page (in your CMS or markdown frontmatter) and use it. Never use new Date() directly.
Listing redirected URLs
A /old-slug that 301s to /new-slug should not be in the sitemap. List the destination only.
Listing noindex pages
If a page has <meta name="robots" content="noindex">, it should not appear in the sitemap. The two signals contradict each other and Google logs the conflict.
Listing 404s
A sitemap with broken URLs gets demoted as a whole. Audit before every release.
Mixing canonical hosts
Pick one. www or no-www. http or https. The whole sitemap must use the same host as the canonical URLs on the pages.
When to split into a sitemap index
Hard limits per sitemap:
- 50,000 URLs
- 50 MB uncompressed
If you approach either, split:
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<sitemap>
<loc>https://yourdomain.com/sitemaps/posts.xml</loc>
<lastmod>2026-05-13</lastmod>
</sitemap>
<sitemap>
<loc>https://yourdomain.com/sitemaps/products.xml</loc>
<lastmod>2026-05-13</lastmod>
</sitemap>
</sitemapindex>In Next.js App Router, use a dynamic sitemap with id segments:
// app/sitemap.ts
export async function generateSitemaps() {
return [{ id: 0 }, { id: 1 }];
}
export default async function sitemap({ id }: { id: number }) {
// return MetadataRoute.Sitemap based on id
}For most sites under 10,000 pages, do not bother. A single sitemap is simpler and cheaper to maintain.
Reference the sitemap from robots.txt
A sitemap that search engines have to guess about is a sitemap that takes longer to discover. Add the line:
Sitemap: https://yourdomain.com/sitemap.xmlGenerate the matching robots.txt with the robots.txt Generator.
Submit to the search consoles
Both major engines accept submission:
Submit once. Both consoles will refetch your sitemap on their own schedule afterward.
changefreq and priority: do they matter?
Officially, Google ignores both. In practice, both engines occasionally use them as weak hints. Cost-benefit:
- Include
changeFrequencyif you can compute it accurately. Default toweeklyfor evergreen anddailyfor news. Skip if you would just guessmonthly. - Include
priorityonly if your site has a clear hierarchy. Otherwise omit.
Neither is worth blocking on. The two signals that actually move the needle are loc (correct canonical) and lastModified (real and stable).
Validation
Run two checks before deploying.
- Local: paste your sitemap into the sitemap validator. It checks structure, namespace, URL count, byte size, dates, changefreq enum, priority range, and per-URL field issues.
- Live: after deploy, confirm with curl.
curl -I https://yourdomain.com/sitemap.xmlYou should see HTTP/2 200 and content-type: application/xml.
What we ship at AgentScan
For reference, this site uses a single sitemap built from a constant build date plus per-blog-post publishedAt for lastModified. The full source is straightforward enough that we never bother with a sitemap index. Total URL count is well under 100. The discipline matters more than the cleverness.
Build yours
Use the sitemap.xml Generator to scaffold a starting body, then move it into app/sitemap.ts and replace the static date with your real build date constant.