From 411dae93f5a156be66aad0f7adc5011d85bab3f4 Mon Sep 17 00:00:00 2001 From: Daniil Gaponov Date: Sun, 6 Sep 2026 13:39:20 +0300 Subject: [PATCH] fix(sitemap): derive locales from i18n config, add x-default and per-locale URLs The sitemap kept its own hardcoded locale list that had drifted from next-i18next.config.js. Three problems followed: - `pt` and `ja` were never submitted, although both are fully translated and already advertised in every page's hreflang set. - No x-default alternate was emitted. - Only the English URL was ever used as , so the eight localized trees were never submitted as first-class URLs, only as alternate annotations hanging off the English entry. Derive the list from the i18n config so it cannot drift again, emit one per (path x locale) with a self-referencing reciprocal alternate set, and add x-default pointing at the English version. pt and ja were verified to be genuine, complete locales before including them: both ship all 19 dictionary files at sizes comparable to de and ru, and their pages render real Portuguese and Japanese. The English meta description they serve is a site-wide issue affecting ru and de equally, not evidence that these routes are unintended. Sitemap goes from 160 to 1440 URLs (1.6 MB), well under Google's 50k/50 MB limits. Verified in dev and a production build: 10 distinct hreflang values, x-default on every entry, 160 per locale, and a 111-URL spread sample across locales all returning 200. Build, lint and typecheck pass. E2E not run - Playwright browsers are not installed locally. Co-Authored-By: Claude Opus 5 (1M context) --- src/pages/sitemap.xml.tsx | 40 ++++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/src/pages/sitemap.xml.tsx b/src/pages/sitemap.xml.tsx index 9a771e3572f0..e9b0a2fe7710 100644 --- a/src/pages/sitemap.xml.tsx +++ b/src/pages/sitemap.xml.tsx @@ -1,6 +1,7 @@ import {GetServerSideProps} from 'next'; import {getServerSideSitemapLegacy} from 'next-sitemap'; +import i18nextConfig from '../../next-i18next.config'; import {libs as componentsLibs} from '../content/components'; import {sections} from '../content/design'; import {libs} from '../libs'; @@ -53,27 +54,40 @@ const generatePaths = () => { return paths; }; +// Derived from the i18n config rather than hardcoded: a separate list had drifted and was +// missing `pt` and `ja`, so those locales were never submitted despite being fully translated +// and advertised in every page's hreflang set. +const {locales, defaultLocale} = i18nextConfig.i18n; + +const localeUrl = (locale: string, path: string) => + locale === defaultLocale ? `${BASE_URL}${path}` : `${BASE_URL}/${locale}${path}`; + export const getServerSideProps: GetServerSideProps = async (ctx) => { const basePaths = generatePaths(); - const supportedLocales = ['', 'ru', 'es', 'zh', 'fr', 'de', 'ko']; - const fields = basePaths.map((pathItem) => { + const fields = basePaths.flatMap((pathItem) => { const {path, notLocalized} = pathItem; - // Generate alternate refs for this path - const alternateRefs = supportedLocales - .filter((locale) => !locale || !notLocalized) // Include default locale always, other locales only if path is localizable - .map((locale) => ({ - href: locale ? `${BASE_URL}/${locale}${path}` : `${BASE_URL}${path}`, - hreflang: locale || 'en', - })); - - return { - loc: `${BASE_URL}${path}`, // Always use the canonical (English) URL as the main loc + const pathLocales = notLocalized ? [defaultLocale] : locales; + + const alternateRefs = [ + ...pathLocales.map((locale) => ({ + href: localeUrl(locale, path), + hreflang: locale, + })), + // Tells Google which version to serve for unmatched languages. + {href: localeUrl(defaultLocale, path), hreflang: 'x-default'}, + ]; + + // One entry per locale, each self-referencing plus the full alternate set. + // Previously only the English URL was listed, so the localized trees were never + // submitted as first-class URLs. + return pathLocales.map((locale) => ({ + loc: localeUrl(locale, path), changefreq: 'daily' as const, priority: 0.7, alternateRefs, - }; + })); }); return getServerSideSitemapLegacy(ctx, fields);