Next.js Docsnextjs
Next JS Sitemap: Building sitemap.xml in the App Router
Add an app/sitemap.ts file, return absolute URLs with honest lastModified dates, and split past 50,000 URLs. Here is how the App Router sitemap really works.
Automatically summarised by AI from Next.js Docs
To build a Next JS sitemap in the App Router, add a file called sitemap.ts (or sitemap.js) to the app directory and export a default function that returns an array of URL objects. Next.js turns that array into a valid sitemap.xml at the root of your site, so there is no plugin to install and no build script to maintain.
If your site is small and rarely changes, you can skip code entirely: a hand-written sitemap.xml placed in the app directory is served as-is. The generated version earns its keep once pages come from a database or a CMS. Each entry is typed as MetadataRoute.Sitemap and accepts a url, lastModified, changeFrequency and priority, plus optional alternates for localised versions, images and videos. A minimal file imports the type from 'next', fetches your posts, and maps each one to an object whose url is the full address and whose lastModified is the date the post was last edited.
That word "full" matters. Google's documentation asks for fully qualified, absolute URLs and says it will try to crawl URLs exactly as listed, so an entry of /blog/my-post instead of https://example.com/blog/my-post is a mistake. The usual pattern is a single BASE_URL constant read from an environment variable, which also stops preview deployments from leaking their temporary hostnames into the production sitemap.
The fields deserve less attention than tutorials give them, with one exception. Google states that it ignores priority and changefreq. It does use lastmod, but only when the value is consistently and verifiably accurate, meaning it reflects the last significant change to the main content, structured data or links. Setting lastModified to new Date() on every entry, which many copy-pasted examples do, tells Google that every page changed today. Do that on every request and the signal quickly becomes worthless. Pull the real updatedAt value from your data instead.
Caching is the next thing to understand. The Next.js docs describe sitemap.js as a special Route Handler that is cached by default unless it uses a Request-time API or a dynamic config option. In practice a sitemap built from a database is frozen at build time unless you opt into revalidation, so a post published after deployment will not appear until the next build. Decide how fresh the file needs to be and configure it deliberately rather than discovering the gap weeks later in Search Console.
Large sites hit a hard ceiling. Under the sitemaps protocol, a single file may hold at most 50,000 URLs and 50MB uncompressed. Next.js gives you two ways to split. The first is to nest sitemap files inside route segments, for example app/sitemap.xml alongside app/products/sitemap.xml. The second is generateSitemaps, which returns an array of objects with an id; each id produces its own file served at a path like /product/sitemap/1.xml, and your sitemap function uses the id to query one slice of rows. Since Next.js 16, that id reaches the sitemap function as a promise that resolves to a string, so code written for version 15 needs an await and a numeric conversion before doing arithmetic with it.
Multilingual sites should use the alternates field. Passing a languages map for each URL makes Next.js emit xhtml:link elements with hreflang values inside the sitemap, which is one of the ways Google accepts to declare language versions. It keeps the relationships in one place instead of scattering link tags across every template, and it is easier to audit.
Finally, tell search engines where the file lives. Google treats a sitemap as a hint, not a command, but you can reference it from robots.txt with a Sitemap: line, and Next.js lets you generate that file too through app/robots.ts. Then submit the sitemap URL in the Search Console Sitemaps report, which shows when Googlebot last fetched it and flags any processing errors. Open the generated /sitemap.xml in a browser after every major deploy: a broken query that returns an empty array still produces a perfectly valid, perfectly useless sitemap, and nothing else will warn you.
Checklist: one sitemap.ts in app, absolute URLs from a single base constant, lastModified taken from real edit dates, no effort spent on priority, a deliberate caching choice, splitting before 50,000 entries, alternates for translated pages, and a Sitemap: line in robots.txt. Get those right and the file does its one job quietly, helping Google find new and changed pages without extra guesswork.
nextjs · Next.js Docs · Published 00:03 · 23 Sept 2026
Read the original ↗