Un site Next.js vraiment multilingue avec next-intl
Routing localisé, rendu statique, hreflang et bascule de langue : comment je structure un site Next.js en dix langues sans sacrifier la performance ni le SEO.
Pourquoi le multilingue est un vrai sujet d'architecture
Traduire une interface, c'est facile. Faire un site multilingue correct — indexable, rapide, maintenable — c'est une décision d'architecture qu'on prend au début, pas à la fin. Ce portfolio existe en dix langues, et voici les choix qui tiennent à l'échelle.
Le routing par segment de locale
L'approche la plus robuste avec l'App Router : un segment dynamique [locale] à la racine. Chaque page vit sous app/[locale]/…, et la langue devient une donnée d'URL de premier ordre (/fr/projects, /en/projects).
Avec next-intl, ce segment est validé au rendu :
- On liste les locales supportées dans une config
routingcentrale. - Une locale inconnue déclenche un
notFound()propre plutôt qu'une page cassée. - La navigation passe par un
Linklocalisé qui préfixe automatiquement la bonne langue.
L'intérêt : les composants ne manipulent jamais la locale « à la main ». Ils demandent une traduction, le système s'occupe du reste.
Le piège du rendu statique
C'est l'erreur qui coûte le plus cher en production. Si vous appelez les traductions sans figer la locale d'abord, next-intl lit les en-têtes de la requête. Résultat : la page bascule en rendu dynamique, et vous perdez le prérendu statique — parfois avec une erreur DYNAMIC_SERVER_USAGE en prod.
La parade tient en une ligne, tout en haut de chaque page :
setRequestLocale(locale);
Appelée avant tout getTranslations, elle garantit un rendu statique complet. Combinée à un generateStaticParams qui croise locale × slug, elle prérend chaque page dans chaque langue au build.
Le multilingue statique n'est pas plus lent que le monolingue. Il est juste plus exigeant sur l'ordre des appels.
Les hreflang, ou comment éviter l'auto-concurrence
Sans indication, Google traite vos dix versions linguistiques comme dix pages rivales sur les mêmes mots-clés. La solution est le hreflang : chaque page déclare l'ensemble de ses variantes.
Deux endroits doivent rester synchronisés :
- Les métadonnées de chaque page — via un helper
generateAlternatesqui produit les balisesalternate. - Le sitemap — chaque URL y liste ses langues et un
x-default.
La cohérence entre ces deux sources est ce qui fait qu'une recherche en espagnol tombe sur /es et pas sur /fr.
La bascule de langue, côté utilisateur
Un sélecteur de langue doit changer la locale sans perdre la page courante. Le réflexe naïf — renvoyer vers l'accueil — est frustrant. Avec le usePathname localisé de next-intl, on récupère le chemin sans préfixe, et on le réémet dans la langue choisie. L'utilisateur reste exactement là où il était.
Ce que je retiens
- Le segment
[locale]en racine est la fondation ; tout en découle. setRequestLocaleavant les traductions : non négociable pour garder le statique.- Les
hreflangse gèrent en double (métadonnées et sitemap), toujours synchronisés. - La direction du texte (RTL pour l'arabe) se pilote au niveau du
<html dir>, pas page par page.
Bien fait, le multilingue devient invisible : chaque visiteur a l'impression que le site a été écrit pour lui, et Google sait exactement quelle porte lui ouvrir.