fix: cache translation lookups to ensure hreflang reciprocity#17864
Conversation
✅ Deploy Preview for ethereumorg ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
myelinated-wackerow
left a comment
There was a problem hiding this comment.
Clean, well-scoped fix. Memoization logic is correct, cache keys are sound, no callers mutate the returned arrays, and the pattern matches existing cachedStaticPages convention. Approve.
Minor observations (non-blocking):
-
Dev server staleness — Module-level caches persist for the process lifetime, so adding/removing translation files during
next devrequires a restart to take effect. Matches existing behavior ofcachedStaticPages. -
Multi-worker builds — If Next.js spawns multiple workers during build, each gets its own cache. Two workers could theoretically see different filesystem states, though the window is much narrower than the current uncached approach.
-
Defensive copy — Current callers are safe, but returning
[...cached]instead of the reference directly would guard against future callers accidentally mutating the cache.
Reviewed by Claude (Opus 4.6)

Summary
getTranslatedLocales()results per slug intranslationRegistry.tsareNamespacesTranslated()filesystem checks per locale+namespace intranslationStatus.tsContext
The March 2026 SEO audit flagged 97 pages with broken hreflang reciprocity (943 flagged rows). Google requires that if Page A declares Page B as an alternate-language version, Page B must declare Page A back.
Root cause:
getTranslatedLocales()andareNamespacesTranslated()use uncachedexistsSync()calls. During a Next.js build, different pages render at different times. If translation files shift mid-build (e.g., concurrent Crowdin sync), different pages can see different filesystem states for the same slug — producing inconsistent locale lists that break hreflang reciprocity.Why it's intermittent: We checked all 10 flagged route+locale combinations against the current production deploy and found all hreflang sets consistent. The issue only manifests when the filesystem changes during a build.
Fix: Module-level
Mapcaches (same pattern as the existingcachedStaticPagesinstaticPages.ts) ensure that once a slug's translated locales are computed, every subsequent caller gets the same result for the lifetime of the build process.Side benefit: Eliminates redundant
existsSync()calls (25 locales × ~5,000 pages = up to 125K filesystem reads reduced to ~5K).Test plan
npx tsc --noEmit)pnpm build)