Skip to content

fix(seo): canoniques cross-locale et URLs noindex au sitemap#264

Merged
thierryvm merged 1 commit into
mainfrom
fix/seo-canonicals-sitemap
Jul 26, 2026
Merged

fix(seo): canoniques cross-locale et URLs noindex au sitemap#264
thierryvm merged 1 commit into
mainfrom
fix/seo-canonicals-sitemap

Conversation

@thierryvm

Copy link
Copy Markdown
Owner

Corrige les 4 défauts SEO préexistants que seo-geo-auditor avait relevés pendant #258. Tous silencieux : rien ne cassait, Search Console aurait juste remonté des erreurs des semaines plus tard.

Ce qui était cassé

Défaut Effet
Canoniques codées en dur (/faq, /legal/*) /en/faq déclarait la page française comme canonique — soit « la version anglaise est un doublon », en contradiction avec le hreflang de la même URL
Index du glossaire sans alternates héritait de la canonique du layout → /glossaire, /en/glossaire et /nl-BE/glossaire pointaient chacun vers leur accueil
3 pages légales noindex au sitemap 15 URLs (3 × 5 locales) garanties « Submitted URL marked noindex »
nl-BE / de-DE / es-ES au sitemap on demandait à Google d'indexer des pages dont le contenu est français verbatim

Mesuré sur build de production

avant après
URLs /legal/* au sitemap 15 0
Canonique de /en/faq /faq /en/faq
Canonique de /en/glossaire l'accueil /en/glossaire
hreflang de l'index glossaire aucun 3 (fr-BE, nl-BE, en)

Ce que l'audit a rattrapé sur mon propre correctif

L'index du glossaire n'émettait que la canonique, sans le bloc languages que sa page sœur [slug] produit déjà. J'aurais donc reproduit sur le correctif lui-même le défaut que la PR ferme : une canonique sans hreflang correspondant. Corrigé.

Second point pris : la locale vient désormais de params et non de getLocale(). Ce dernier ne fonctionnait que parce que le layout appelle cookies() pour le thème, ce qui force le rendu dynamique — un couplage implicite à un détail sans rapport.

Tests

tests/seo/sitemap.test.ts (5 specs) verrouille : aucune route noindex, aucune locale non traduite hors glossaire, pas de doublon d'URL, et aucun hreflang pointant vers une URL absente du sitemap. Falsifiabilité vérifiée : en réintroduisant l'une ou l'autre régression, 2 specs passent au rouge.

npm run test 1674/1674 · typecheck 0 · lint 0 (7 warnings préexistants)

Volontairement hors périmètre

  • Le header Link du middleware next-intl annonce toujours les 5 locales, plus large que ce que le sitemap soumet désormais. Le corriger implique proxy.ts, que la note de routing.ts réserve à une PR dédiée.
  • robots.txt ne doit pas bloquer /legal/ : Google ne pourrait plus lire le noindex qui les retire réellement.
  • public/llms.txt annonce toujours le néerlandais comme langue opérationnelle — correctif d'une ligne, tracé à part.
  • Asymétrie assumée : le glossaire reste indexé en nl-BE (ses termes sont réellement traduits) alors que la landing ne l'est plus.

… noindex URLs

Four defects seo-geo-auditor surfaced during #258, all pre-existing and all
silent — nothing failed, Search Console would simply have reported errors weeks
later.

Cross-locale canonicals. faq and the three legal pages hardcoded
`alternates: { canonical: '/faq' }`, never prefixed. When a segment declares
alternates, Next REPLACES the parent value instead of recomputing it per
locale, so /en/faq emitted a canonical pointing at the French page — telling
Google the English version is a duplicate, while the hreflang header for that
same URL said otherwise.

The glossary index declared no alternates at all and therefore inherited the
parent layout's canonical: /glossaire, /en/glossaire and /nl-BE/glossaire each
canonicalised to their own homepage. Its sibling [slug] page did this
correctly; the index never did. It now emits the same `languages` block too —
omitting it would have repeated, on the fix itself, the canonical-without-
hreflang defect this commit closes.

Noindex pages in the sitemap. The three legal pages set robots.index=false yet
were submitted for all five locales: 15 URLs guaranteed to come back as
"Submitted URL marked noindex". They are out of PUBLIC_ROUTES. robots.txt is
deliberately left alone — disallowing them would stop Google reading the very
noindex tag that removes them.

Untranslated locales. The sitemap advertised nl-BE, de-DE and es-ES for the
marketing routes while their landing copy is still French verbatim. It now
iterates LOCALES_VISIBLE. The glossary keeps its own wider GLOSSARY_LOCALES
scope, because those three locales genuinely have translated terms — an
asymmetry worth stating rather than silently aligning.

Locale now comes from `params` rather than `getLocale()`. The latter worked
only because the locale layout happens to call `cookies()` for the theme, which
forces dynamic rendering; reading the segment removes that hidden coupling and
matches the glossary pages.

tests/seo/sitemap.test.ts locks all of it: no noindex route, no untranslated
locale outside the glossary, no duplicate URL, and no hreflang pointing at a
URL the sitemap omits. Verified falsifiable — restoring either regression turns
two specs red.

Measured on a production build: 0 legal URLs in the sitemap, /en/faq now
canonicalises to itself, and the glossary index emits its three hreflang links.

Left out on purpose: next-intl's middleware Link header still advertises all
five locales, which is broader than what the sitemap now submits. Fixing it
means touching proxy.ts, which the routing note reserves for a dedicated PR.
public/llms.txt still claims Dutch is an operating language; a one-line fix,
tracked separately rather than smuggled in here.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @thierryvm, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@vercel

vercel Bot commented Jul 26, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
ankora Ready Ready Preview, Comment Jul 26, 2026 12:31am

@github-actions github-actions Bot added area:seo SEO / meta / sitemap status:review-needed Ready for review type:fix Bug fix labels Jul 26, 2026
@thierryvm
thierryvm merged commit 3a16960 into main Jul 26, 2026
9 checks passed
@thierryvm
thierryvm deleted the fix/seo-canonicals-sitemap branch July 26, 2026 00:45
thierryvm added a commit that referenced this pull request Jul 26, 2026
…SEO (#265)

Handoff de fin de session. Documentation uniquement, 1 fichier.

**Livré** : #261 (redirections localisées), #262 (registre des
destinations de nav), #263 (sélecteur de langue), #264 (SEO) — toutes
mergées.

**Reste à faire, ordonné** : le lot 2 de la refonte UX en tête, avec les
trois limites que le lot 1 s'est explicitement interdit de traiter
reprises comme entrées plutôt que laissées à re-découvrir. Puis la
reconnexion fantôme, sans diagnostic à ce jour. Puis les dettes tracées.

**Frictions d'environnement consignées** parce qu'elles ont coûté du
temps deux fois chacune : `playwright.config.ts` ne charge pas
`.env.local`, `e2e:auth` sert un build prod où le rate limit échoue en
fermé sur l'Upstash factice, la variable est `E2E_BASE_URL`, et le port
3000 appartient à un autre projet.

Miroir dans le vault Obsidian, slug vérifié via `_index.md`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:seo SEO / meta / sitemap status:review-needed Ready for review type:fix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant