Splitting a Next.js Sitemap into 200-URL Child Files
How I split a Next.js sitemap into 200-URL child files with generateSitemaps, listed them in robots.txt, and what Google actually reads from a sitemap.
The 404 that taught me Next.js emits no sitemap index
I wired generateSitemaps() into the blog's sitemap.ts, deployed, then opened /sitemap.xml to submit it to Search Console. 404. The child files were sitting right there at /sitemap/0.xml, but nothing pointed at them, so Googlebot had no entry point and would never discover the articles.
This isn't my bug. Next.js genuinely does not emit a root /sitemap.xml index when you use generateSitemaps() (tracked in vercel/next.js#77304). The framework hands you the child URLs; wiring them into something crawlable is on you.
The fix is a robots.txt you maintain yourself
So robots.ts lists every child file explicitly: Sitemap: https://adityo.web.id/sitemap/0.xml, then /sitemap/1.xml, and so on. Search Console reads those and crawls each. The split itself is a 200-URL chunk: static routes always land in file 0, published articles follow, walked from the list API across every locale. The ordering has to stay deterministic, because generateSitemaps() computes Math.ceil(total / 200) and sitemap(id) slices the same array. If those ever diverge, a URL silently jumps chunks between runs and you lose crawl continuity. Each article entry carries hreflang alternates built from its translation_group, with x-default pinned to en.
One resilient touch: when the article API is unreachable, the sitemap degrades to static routes instead of throwing a 500. A broken sitemap is worse than a partial one.
Why chunk, and what Google actually reads
Google caps a single sitemap at 50,000 URLs or 50MB. This blog is small today, but articles accrue; chunking now is the protocol-correct shape rather than a later scramble. The more useful lesson is about the XML itself: Google ignores <priority> and <changefreq> entirely. Only <lastmod> feeds recrawl scheduling, and only when it's verifiably accurate. So I set lastmod from the article's updated_at, not the build timestamp. Stamping "now" on every entry teaches Google to distrust your dates, which is exactly the opposite of helpful. The sitemap ping endpoint was deprecated back in June 2023 anyway, so robots.txt Sitemap: lines or direct Search Console submission is the only path that matters now.
I'd rather hand-maintain the robots list than bolt on an index route that fights Next's file conventions. Fewer moving parts, and the failure mode is graceful instead of a hard 500. If you want the longer version of how this blog is wired, the Vercel migration write-up and the branch strategy cover the rest.