< BACK Vieille carte en papier usé de l'Europe sur un bureau en bois avec une tasse d'espresso, lumière éditoriale grise, grain de film 35 mm

Next.js i18n en 2026 : routage, hreflang et SEO

En 2023, j'ai lancé un site e-commerce multilingue pour un client basé à Berlin. Douze langues, App Router, tout semblait propre. Trois mois plus tard, leurs pages de produits en allemand se classaient dans les SERP britanniques et leurs pages en anglais étaient servies aux utilisateurs à Munich. Les balises hreflang étaient techniquement présentes. Elles étaient juste mauvaises d'une manière qui m'a pris un après-midi embarrassant à démêler. Cette expérience m'a appris plus sur Next.js i18n que n'importe quelle page de documentation.

Ce n'est pas un post « voici les docs officielles résumées ». C'est ce que j'ai appris en construisant des sites multilingues chez Seahawk Media, ce qui pose problème à répétition, et les décisions spécifiques qui affectent vraiment les classements en 2026.

---

L'App Router a Changé la Donne (Et pas que en bien)

Le Pages Router avait un support i18n natif. Vous laissiez tomber un bloc dans next.config.js et Next.js gérait la détection de locale, le routage par sous-chemin et le changement de locale du composant Link presque automatiquement. Ce n'était pas parfait, mais c'était directif, ce qui signifiait moins de pièges.

L'App Router a supprimé le routage i18n natif entièrement. Pas de clé i18n dans next.config.js. Pas de détection automatique de locale. Vous êtes maintenant responsable de toute la logique de routage, et c'est là que la plupart des équipes font discrètement du désordre.

L'approche recommandée en 2026 est un segment dynamique [locale] à la racine de votre répertoire app/. Quelque chose comme app/[locale]/page.tsx. Associez cela à next-intl ou i18next via react-i18next, un fichier middleware qui lit les en-têtes Accept-Language et redirige en conséquence, et vous avez une base fonctionnelle.

Pourquoi le Middleware Est Mal Compris

Le fichier middleware fait deux tâches distinctes que les gens confondent. Premièrement, la détection de locale : lire l'en-tête Accept-Language (ou un cookie, ou un préfixe d'URL) et décider quelle locale servir. Deuxièmement, rediriger ou réécrire l'URL pour que l'utilisateur se retrouve à /de/produkte plutôt qu'à /produkte avec un état de locale caché.

Les deux doivent se produire avant que la page ne s'affiche. Ça semble évident. Mais j'ai vu des équipes exécuter la détection de locale à l'intérieur d'un fournisseur de contexte React, ce qui signifie que le HTML initial servi à Googlebot n'a aucun signal de locale du tout. Le bot voit une page, l'indexe sous l'URL racine, et soudain vous avez un désordre de canoniques.

Gardez la résolution de locale dans le middleware. Toujours.

---

Sous-chemin vs Sous-domaine : Choisissez Un et Engagez-Vous

Le débat éternel. /en/about vs en.yoursite.com/about. Pour la plupart des projets je recommande le routage par sous-chemin, et voici pourquoi : il concentre votre autorité de domaine. Chaque lien pointant vers votre .com bénéficie à toutes les locales. Avec les sous-domaines vous exécutez effectivement des sites séparés aux yeux de Google, ce qui signifie que vous devez construire l'autorité pour chacun indépendamment.

Il y a une exception. Si vous êtes sur un marché où le domaine de premier niveau local compte énormément pour la confiance (.de en Allemagne, .fr en France), un domaine de premier niveau avec code pays surpasse à la fois les sous-chemins et les sous-domaines pour la crédibilité locale perçue. Ce n'est pas une affirmation SEO, c'est une affirmation de conversion. J'ai vu des tests A/B où .de vs yourbrand.com/de ont déplacé les métriques de confiance de manière significative même quand les classements étaient identiques.

Mais vous ne pouvez pas facilement configurer un ccTLD dans une seule application Next.js. Donc pour 99% des projets : des sous-chemins, une application, un domaine.

---

hreflang : L'étiquette qui vous ment

C'est là que je vois le plus de dégâts. hreflang n'est pas difficile conceptuellement. Elle dit aux moteurs de recherche : « cette page a des équivalents dans d'autres langues, les voici ». En pratique, l'implémentation a trois modes de défaillance spécifiques que j'observe constamment.

Mode de défaillance 1 : Les étiquettes ne sont pas réciproques

Chaque page dans votre ensemble hreflang doit référencer toutes les autres pages, y compris elle-même. Si /en/about a une hreflang pointant vers /de/about mais que /de/about n'a pas une hreflang pointant en retour vers /en/about, Google ignore l'ensemble. Pas de pénalité. Ignore. Comme si les étiquettes n'existaient pas.

Seahawk avait un projet fintech l'année dernière où 40% de leurs étiquettes hreflang n'étaient pas réciproques parce que deux développeurs les avaient implémentées indépendamment dans différents composants sans coordination. La correction a pris une heure. La récupération des classements a pris deux mois.

Mode de défaillance 2 : L'étiquette x-default manque ou est mal placée

x-default est pour les pages qui ne ciblent pas une langue ou une région spécifique. Généralement votre racine / ou une page de sélecteur de langue. Certaines équipes mettent x-default sur leur page anglaise, ce qui est correct si l'anglais est vraiment votre fallback pour tous les locales non reconnus. Mais si vous servez une page différente aux utilisateurs dont vous ne supportez pas la langue, x-default devrait pointer vers celle-ci.

J'ai vu x-default omis entièrement. Google ne lance pas d'erreur. Il prend simplement sa propre décision sur la version à afficher pour les requêtes non reconnues, et cette décision est souvent mauvaise.

Mode de défaillance 3 : Barres obliques finales et conflits canoniques

Votre balise hreflang indique https://yoursite.com/de/ueber-uns . Votre balise canonique sur la même page indique https://yoursite.com/de/ueber-uns/ (notez la barre oblique finale). Google traite ces deux URLs comme différentes. Votre balise canonique et votre hreflang pointent maintenant vers des choses différentes, et vous avez créé un conflit de signal qui supprime silencieusement les classements sans aucune erreur visible dans Search Console.

Définissez trailingSlash dans next.config.js et soyez cohérent partout. Vérifiez-le. Vérifiez-le réellement en envoyant des requêtes curl à vos propres URLs et en lisant le résultat.

La documentation Google Search Central sur hreflang vaut vraiment la peine d'être lue en intégralité. C'est l'un de ces rares documents officiels assez spécifiques pour être utile.

---

Générer hreflang à l'échelle dans Next.js

Pour un site en 5 langues, vous pourriez écrire les balises hreflang à la main. Pour 12 locales sur 3 000 pages de produits, vous avez besoin d'une approche systématique.

Voici ce que j'utilise :

  1. Définissez votre liste de locales une fois, dans un seul fichier de configuration. Quelque chose comme locales.config.ts qui exporte un tableau : ['en', 'de', 'fr', 'es', 'nl'...].
  2. Construisez une fonction utilitaire getHreflangAlternates(pathname: string) qui prend un chemin indépendant de la locale (/about ) et retourne un tableau d'objets { hreflang, href } pour chaque locale.
  3. Appelez cette fonction dans votre root layout.tsx et injectez les résultats via l'API metadata de Next.js en utilisant le champ alternates.languages. Cette fonctionnalité a été ajoutée dans Next.js 13.3 et elle génère automatiquement les balises <link rel="alternate" hreflang="..."> correctes dans <head>.
  4. Pour les pages avec du contenu dynamique (articles de blog, pages produit) où toutes les locales n'ont pas de traduction, passez un tableau supportedLocales à votre utilitaire et générez les balises uniquement pour les locales où le contenu existe réellement. Une balise hreflang pointant vers une page qui retourne 404 est pire qu'aucune balise hreflang.

L'approche alternates.languages dans l'API metadata est le moyen le plus propre que j'ai trouvé pour gérer cela. Pas de manipulation custom de <Head>, pas de packages tiers pour les balises elles-mêmes.

---

Détection de locale sans être trop intrusif

Il y a un piège UX ici qui a aussi des implications SEO. La redirection automatique agressive basée sur la locale du navigateur est mauvaise pour les deux.

Si un utilisateur en Allemagne visite yoursite.com/en/article et que votre middleware le redirige en dur vers /de/artikel sans demander, vous venez de casser sa navigation intentionnelle. Vous avez également potentiellement cassé le partage de lien : quelqu'un partage l'URL en anglais, le destinataire en France est redirigé vers le français, et le contenu qu'il était censé voir n'existe peut-être pas encore en français.

Ma règle : détectez, suggérez, ne forcez pas. Utilisez l'en-tête Accept-Language pour définir une locale par défaut sensée à la première visite (stockée dans un cookie), mais laissez les utilisateurs la remplacer manuellement et respectez ce remplacement à chaque visite suivante. Le cookie prend la priorité sur l'en-tête.

Pour le SEO spécifiquement : Googlebot n'envoie pas d'en-têtes Accept-Language significatifs. Il crawlera vos URLs telles quelles. Alors assurez-vous que chaque URL de locale est directement accessible sans redirection, et que votre sitemap inclut explicitement toutes les variantes de locale.

La documentation du middleware next-intl couvre bien cette stratégie de détection, et c'est vers là que je dirigerais quiconque démarre un nouveau projet.

---

Plans de site pour les sites multilingues

Un seul sitemap.xml qui inclut chaque variante de langue fonctionne bien pour les petits sites. Pour les grands sites (des dizaines de milliers d'URL sur plusieurs langues), je le divise : un index de sitemap à /sitemap.xml, puis des fichiers sitemap séparés par langue à /sitemaps/en.xml, /sitemaps/de.xml, et ainsi de suite.

Chaque entrée d'URL doit inclure des balises <xhtml:link rel="alternate"> qui reflètent votre configuration hreflang. C'est redondant avec les balises in-page, mais cela aide Googlebot à faire le lien sur les pages qu'il n'a pas encore parcourues.

Avec Next.js 14+, vous pouvez générer les sitemaps dynamiquement via app/sitemap.ts. Le type de retour supporte alternates.languages avec la même structure que l'API metadata. Connectez-le à la même fonction getHreflangAlternates que vous avez construite plus tôt et vous obtenez la cohérence gratuitement.

Un chiffre concret : sur un projet avec 8 langues et 4 500 produits, la division des sitemaps a réduit le temps de crawl moyen par langue d'environ 30% selon les statistiques de crawl de GSC. Pas révolutionnaire, mais significatif.

---

Bibliothèques de traduction : ce que j'utilise vraiment

Il y en a trois qui méritent d'être connues. next-intl est mon choix par défaut pour les projets App Router. Il a un support de première classe pour les server components, gère correctement la pluralisation, et l'API est propre. Je l'ai utilisée sur environ 40 projets depuis mi-2023.

react-i18next est éprouvé au combat et dispose d'un écosystème immense, mais son modèle mental a été construit pour les client components et ajouter le support des server components via le cœur d'i18next semble bricolé comparé à l'approche native de next-intl.

Lingui mérite le coup d'œil si votre workflow de traduction implique des traducteurs professionnels plutôt qu'un CMS. Son outillage d'extraction est supérieur. Mais pour la plupart des projets d'agence où le contenu provient de Contentful ou Sanity, next-intl est plus simple.

Ce que j'éviterais : développer le vôtre. Je l'ai fait une fois en 2020 pour un projet où le client insistait pour réutiliser directement ses fichiers de traduction PHP existants. Ça a marché, tant bien que mal, et j'ai passé deux ans à maintenir quelque chose que next-intl gère maintenant en un simple appel useTranslations().

---

FAQ

Next.js App Router supporte-t-il l'ancien bloc `i18n` dans `next.config.js` ?

Non. La clé de configuration i18n était spécifique au Pages Router. Si vous migrez vers App Router, vous devez implémenter le routage via un segment dynamique [locale] et un fichier middleware. L'ancien bloc de config est silencieusement ignoré dans les projets App Router.

Dois-je utiliser `lang` dans le chemin d'URL ou me fier aux sous-domaines ?

Pour la plupart des projets, lang dans le chemin d'URL (routage en sous-chemin comme /de/) est le bon choix. Les sous-domaines fragmentent votre autorité de domaine et nécessitent une configuration DNS séparée. Les ccTLD ne valent la complexité que si les signaux de confiance locale font réellement bouger les taux de conversion dans vos marchés spécifiques.

Comment Googlebot gère-t-il le contenu de locale rendu en JavaScript ?

Mal, parfois. Si votre détection de locale s'exécute purement côté client dans un contexte React et que le HTML initial n'a aucun marqueur de locale, Googlebot peut indexer l'état pré-hydratation. Résolvez toujours la locale dans le middleware et assurez-vous que l'attribut `lang` correct est sur l'élément <html> dans le HTML rendu côté serveur. Vérifiez cela en affichant la source, pas en utilisant les devtools du navigateur (qui affichent l'état post-hydratation).

Quelle est la bonne valeur `x-default` hreflang pour un site sans page de sélecteur de langue ?

Pointez-la vers la page d'accueil de votre locale principale, généralement /en/. Cela signale à Google que l'anglais est votre langue de repli pour les utilisateurs dont vous ne supportez pas la langue. Ce n'est pas une pénalité de le faire, c'est juste un signal de préférence faible que Google peut ou non honorer.

Puis-je vérifier mon implémentation hreflang sans un outil SEO payant ?

Oui. L'outil Inspection des URL de Google Search Console vous affichera les balises hreflang détectées pour toute URL indexée. Il ne validera pas automatiquement la réciprocité, mais vous pouvez vérifier manuellement quelques pages représentatives. Pour la validation en masse, Screaming Frog (gratuit jusqu'à 500 URLs) gère les vérifications de réciprocité dans son onglet hreflang.

---

Bien faire du Next.js multilingue est un travail véritablement peu glamour. C'est des fichiers de configuration, de la cohérence d'URL, et s'assurer que deux développeurs n'ont pas implémenté la même chose deux fois dans des composants différents. Mais les sites où j'ai pris le temps de le faire correctement ont des classements qui tiennent bon sur les régions, et ceux où j'ai coupé les coins ont m'ont enseigné les leçons que j'ai écrites ici.

Le client berlinois, d'ailleurs, a finalement réussi à faire classer ses pages en allemand en Allemagne. Cela nous a pris un cycle de recrawl complet et beaucoup de café.

< BACK