Skip to content

fix: shard sitemap.xml per locale#18812

Open
myelinated-wackerow wants to merge 1 commit into
fix/sitemap-staticfrom
fix/sitemap-shards
Open

fix: shard sitemap.xml per locale#18812
myelinated-wackerow wants to merge 1 commit into
fix/sitemap-staticfrom
fix/sitemap-shards

Conversation

@myelinated-wackerow

Copy link
Copy Markdown
Collaborator

Summary

Part 2 of #18014, stacked on #18802 (base branch is fix/sitemap-static, not dev — review that first). Splits the sitemap into one shard per locale so it clears Google's per-file limits.

Why

Even after #18802 makes the sitemap build statically (complete, no truncation), it's still a single ~51 MB document with ~17.5k URLs and ~455k hreflang alternates — over Google's 50 MB / 50k-URL per-file limits, so crawlers reject it outright. Sharding is required for the sitemap to be accepted at all.

Changes

  • app/sitemap.ts: generateSitemaps() emits one shard per locale (id = locale). The default sitemap({ id }) filters pages to that locale and keeps the full hreflang alternates block, so cross-language reciprocity is preserved. A memoized traversal lets all shards share a single content walk.
  • app/robots.ts: lists every /sitemap/<locale>.xml shard. Next serves shards at /sitemap/<id>.xml and emits no index file, so robots.txt is the discovery mechanism; this replaces the old single /sitemap.xml reference.

Verification (full 25-locale build)

  • All 25 shards prerender static (initialRevalidateSeconds: false in the prerender manifest — no ISR).
  • Every shard closes with </urlset>, carries ~700 URLs, and includes the sentinel /wallets/find-wallet/ (the page that was invisible to crawlers in prod).
  • Total across shards: 17,087 URLs. Largest shard ~2.01 MB (pt-br/zh-tw/en) — well under 50 MB, ~700 URLs each vs the 50k cap.

Post-merge follow-up (not code)

This removes the root /sitemap.xml. robots.txt handles crawler auto-discovery, but the sitemap registered in Google Search Console points at /sitemap.xml and should be updated to the shard URLs so GSC stops hitting a 404. Nothing in the codebase depends on the old path.

A CI regression guard (parse each shard: valid XML, closing </urlset>, URL-count floor, sentinel late-alphabet URL) will follow as a separate PR — this is the check that was missing when #17718 was closed on a single lucky fetch.

-- Opus 4.8

Even served statically, a single sitemap document is ~51MB / 17.5k URLs -- over Google's 50MB and 50k-URL per-file limits -- so crawlers reject it outright. Split it into one shard per locale via generateSitemaps(), each ~2MB / ~700 URLs, comfortably under both limits. Each shard still emits the full hreflang alternates block so cross-language reciprocity is preserved.

Next serves the shards at /sitemap/<locale>.xml and emits no index file, so robots.ts now lists every shard for crawler discovery in place of the old /sitemap.xml. A memoized traversal keeps all shards sharing a single content walk.

Verified against a full 25-locale build: all shards prerender static (initialRevalidateSeconds: false, no ISR), close with </urlset>, carry ~700 URLs each including /wallets/find-wallet/, and the largest is ~2MB.

Part 2 of #18014. Note: this removes the root /sitemap.xml; whatever is registered in Google Search Console should be updated to the shard URLs (robots.txt covers auto-discovery regardless).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: wackerow <54227730+wackerow@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔎 First-pass review — ✅ Looks mergeable

Splits the sitemap into one shard per locale via generateSitemaps() (id = locale), keeps the full hreflang alternates block on every URL, and points robots.ts at the per-locale shard list. This is a sound, minimal way to get under Google's 50 MB / 50k-URL per-file caps, and the logic checks out: generateSitemaps() and the robots.ts shard list are both driven by LOCALES_CODES, so they cannot drift; the per-shard translatedLocales.includes(locale) filter partitions the same URL set the old single document emitted (total URL count is conserved, matching the ~17k figure). No convention issues — imports all used, no raw Intl.*, memoization is safe under force-static.

Note for reviewers: base branch is fix/sitemap-static (stacked on #18802) — review/merge that first. The GSC-registered /sitemap.xml will 404 after this ships; the described post-merge GSC update is required.

Analysis
  • Lane: code (app/sitemap.ts, app/robots.ts).
  • Checked: shard/robots consistency (both use LOCALES_CODES), URL-count conservation vs. the pre-shard doc, hreflang alternates preserved per URL, per-shard dedup (seenUrls), memoized traversal correctness under force-static, unused imports, chain-name / Intl wrapper conventions.
  • Non-blocking: id is typed Promise<string> and awaited — runtime-safe either way; Next 16's generated route type check on the sitemap default export will confirm the signature (let CI settle). Empty shards for zero-page locales still emit valid <urlset></urlset>.
  • Follow-up (author-noted, not code): CI regression guard parsing each shard is coming as a separate PR.

Generated by PR Reviewer (team) for #18812 · 88 AIC · ⌖ 24.2 AIC · ⊞ 5.2K ·

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant