Aller au contenu
Neaptidestudio
blog

Balises hreflang sous Next.js avec traductions partielles : montage testé

Neaptide · 4 octobre 2026 · 9 min de lecture

Test reproductible sous Next.js et contrôle de 18 URL en ligne : aligner hreflang, canonical, sitemap et sélecteur de langue quand une traduction manque.

Dans cet article
Le profil full est publié en en, ru, fr, es, it et zh ; partial en en, ru et fr ; original uniquement en en : dix combinaisons publiées et huit absentes.

Générez les liens linguistiques d’une page à partir de ses traductions effectivement publiées, et non de la liste globale des langues prises en charge par le site. Appuyez-vous sur cette même table de disponibilité pour les balises hreflang, le sitemap et le sélecteur de langue : la présence du français dans vos locales globales ne justifie pas d’exposer une URL française pour un article non traduit.

Nous avons mis ce principe à l’épreuve le 4 octobre 2026. Un projet Next.js volontairement défectueux a généré 14 balises hreflang et 14 liens de navigation pointant vers des traductions inexistantes, ainsi que huit URL introuvables dans son sitemap. Le passage à une disponibilité déclarée page par page a éliminé ces trois séries d’incohérences. En parallèle, 18 URL publiées sur le site de Neaptide ont validé les contrôles décrits plus bas. Aucun de ces deux jeux de données ne mesure l’indexation ou les positions dans Google.

Deux jeux de preuves aux rôles bien distincts

L’échantillon en production couvre trois routes déclinées en six langues : la page de service GEO, le guide du site multilingue et l’article consacré au catalogue B2B. Nous avons effectué deux passages HTTP, inspecté chaque page rendue et testé une action sur le sélecteur de langue par URL. Une requête a subi une réinitialisation de connexion ; un nouvel essai consigné à part a abouti, soit 37 tentatives pour 36 réponses valides — et non un premier passage sans incident.

Pages Neaptide en production : six URL localisées par route
RouteURL contrôléesMétadonnées + sitemapActions du sélecteur
/services/geo66 / 66 / 6
/blog/multilingual-website-guide66 / 66 / 6
/blog/b2b-catalog-seo66 / 66 / 6

Les 18 URL ont répondu en HTTP 200, conservé une balise canonical auto-référente et concordé avec leurs annotations linguistiques dans le sitemap : chacune déclarait six variantes de langue ainsi que l’anglais en x-default. Les 18 actions testées sur le sélecteur ont toutes préservé la route cible. Ce contrôle porte sur une transition par URL source — non sur l’ensemble des paires de langues possibles ni sur tout le site — et examinait la version publiée de l’article B2B, non son brouillon local plus récent.

Test contrôlé sur des traductions incomplètes

Nous avons monté un projet App Router dédié sous Next.js 15.5.22, React 19.2.6 et Node.js v25.2.1. Trois profils ont été figés avant les mesures : six traductions publiées, trois traductions publiées et la version originale anglaise seule. Chaque phase totalise 18 combinaisons route/langue, soit 10 pages existantes et huit manquantes. Deux passages HTTP avant et après correction ont produit 72 observations, complétées par l’ouverture des 36 combinaisons dans un navigateur.

Le profil full est publié en en, ru, fr, es, it et zh ; partial en en, ru et fr ; original uniquement en en : dix combinaisons publiées et huit absentes.
Environnement de test contrôlé, et non échantillon du marché. Les cellules vertes disposent d’un contenu publié ; les cellules vides ont renvoyé 404 lors des deux phases.

Lors de la phase défectueuse, les trois sorties s’appuyaient sur la liste globale des six langues ; lors de la phase corrigée, elles lisaient uniquement les traductions existantes. Les pages publiées ont conservé une balise canonical auto-référente de bout en bout, sans texte de substitution ni redirection des traductions manquantes vers la page d’accueil.

Anomalies mesurées sur le projet volontairement défectueux
ProfilLiens hreflang invalides : avant → aprèsLiens du sélecteur invalides : avant → aprèsURL introuvables dans le sitemap : avant → après
6 langues0 → 00 → 00 → 0
3 langues9 → 09 → 03 → 0
1 langue5 → 05 → 05 → 0

Attention aux unités : un lien erroné est comptabilisé par page source, tandis qu’une URL de sitemap ne compte qu’une fois par entrée ; ne les additionnez pas pour obtenir un nombre de pages cassées. Les deux passages HTTP ont concordé, tout comme les 36 inspections du DOM face au HTML brut. Cinq clics représentatifs ont confirmé trois changements de langue valides et deux liens volontairement cassés aboutissant sur une erreur 404. Cette expérience ne mesure pas la fréquence de ce défaut en production.

S’appuyer sur une table unique de disponibilité

Dans le projet de test, l’objet TRANSLATIONS ne répertorie que les contenus publiés : availableLocales en extrait les clés, et generateStaticParams ne pré-génère que ces routes. Avec dynamicParams = false, les combinaisons manquantes restent inaccessibles, tandis que les fonctions de page et de métadonnées vérifient elles aussi la présence de l’entrée. L’extrait ci-dessous reprend les utilitaires exacts du banc d’essai. L’archive téléchargeable inclut les deux phases : consultez la branche after pour étudier l’implémentation corrigée.

export const availableLocales = profile => Object.keys(TRANSLATIONS[profile] || {});
export const translation = (profile, locale) => TRANSLATIONS[profile]?.[locale];
export const pageUrl = (profile, locale, phase) => `${ORIGIN}/${locale}/${phase}/${profile}`;
export async function generateMetadata({ params }) {
  const { phase, profile, locale } = await params;
  const content = translation(profile, locale);
  if (!PHASES.includes(phase) || !content) notFound();
  return {
    title: content.title,
    alternates: { canonical: pageUrl(profile, locale, phase), languages: languageAlternates(profile, phase) },
  };
}

Dans la branche after, la fonction languageAlternates ne retourne que les équivalents publiés : elle désigne l’anglais comme x-default sur les profils multilingues et omet les annotations hreflang lorsqu’il n’existe que l’original anglais. Ce repli résulte d’un choix explicite de notre part : si votre contenu source est rédigé dans une autre langue, sélectionnez une destination existante adaptée à ce groupe. Enfin, si vos slugs varient d’une langue à l’autre, stockez directement les URL complètes au lieu de substituer le préfixe de langue (les slugs traduits n’ont pas été testés ici).

Séparer le rôle de canonical, de hreflang et de la navigation

La balise canonical identifie l’URL de référence d’une page, tandis que hreflang relie entre elles ses variantes localisées. Nos pages traduites utilisent toutes une balise canonical auto-référente. Le sélecteur de langue doit conduire le lecteur vers l’équivalent traduit réellement disponible ; un éventuel lien clairement intitulé vers l’accueil d’une autre langue relève de la navigation ordinaire, et non d’une traduction de la page absente.

Google demande des liens linguistiques absolus et réciproques incluant la page elle-même. Les balises HTML et les annotations dans le sitemap constituent deux méthodes alternatives ; nous avons vérifié la cohérence des deux puisque ce projet les emploie simultanément.

Que faire lorsqu’une traduction n’est pas encore prête

Retirez la langue manquante du groupe d’annotations hreflang ainsi que de la liste des pages équivalentes du sélecteur, et gardez tout brouillon hors du sitemap public. Si vous proposez un lien de repli vers l’original anglais, annoncez explicitement cette langue et pointez vers sa véritable URL anglaise : servir en HTTP 200 un texte en anglais sous un chemin français ne crée pas pour autant une traduction française.

Sur notre banc d’essai statique, les huit combinaisons absentes ont renvoyé un code 404 accompagné de noindex lors des deux phases, ce que nous avons vérifié sur les réponses HTTP réelles. Une architecture en rendu dynamique ou en streaming exige son propre contrôle des codes HTTP : voir un appel à notFound() dans le code source ne remplace jamais une vérification sur la réponse servie.

Contrôler le résultat rendu après chaque mise en ligne de traduction

  1. Ouvrir chaque variante publiée et contrôler son code HTTP, son URL finale, sa langue déclarée et sa balise canonical.
  2. Confronter chaque lien alternatif annoncé à la table de disponibilité, puis vérifier l’accessibilité de la cible et la réciprocité des liens de retour.
  3. Comparer les entrées effectives du sitemap aux contenus publiés et, s’il embarque des annotations linguistiques, vérifier leur exactitude face au HTML.
  4. Actionner le sélecteur de langue depuis les pages concernées afin de valider aussi bien l’accès à une traduction existante que le comportement face à une langue absente.
  5. Rejouer ces contrôles après tout ajout ou retrait de traduction en archivant la date, la route et les preuves de sortie, puis suivre séparément l’indexation dans la Search Console.

Reproduire l’expérience

Téléchargez le code source du banc d’essai, les résultats relevés et le protocole complet. L’archive ZIP exclut les dépendances et les fichiers de build : compilez et démarrez le projet en local sur le port documenté, puis exécutez le collecteur HTTP fourni. Les vérifications initiales effectuées dans le navigateur sont archivées séparément ; le collecteur ne prétend pas les rejouer.

Ce test technique a été mené sur une même journée par un seul chercheur. Il porte sur un échantillon de trois routes en production et sur des environnements statiques contrôlés : il n’évalue ni la qualité des traductions à l’échelle du site, ni les URL canoniques retenues par Google, ni l’indexation, les positions, le trafic ou les citations par les moteurs IA.

faq

L’essentiel en bref

Les balises hreflang doivent-elles lister toutes les langues du site ?

Non : ne déclarez que les équivalents effectivement publiés pour cette page précise. Une liste globale de langues à l’échelle du site ne prouve pas que chaque page existe dans chacune d’elles.

Toutes les traductions doivent-elles pointer leur balise canonical vers la version anglaise ?

Non : nos pages traduites conservent chacune leur propre URL canonique auto-référente. Ne désignez pas l’original anglais comme URL canonique d’une traduction distincte au seul motif qu’il a été publié en premier.

L’attribut x-default doit-il obligatoirement pointer vers l’anglais ?

Non. L’anglais correspond simplement au repli existant que nous avons retenu pour ce banc d’essai. Choisissez une page réellement disponible et pertinente pour vos visiteurs comme pour votre groupe de traductions.

La suppression des liens invalides a-t-elle amélioré le positionnement SEO ?

Nous n’avons pas mesuré les positions. Le résultat observé réside dans la disparition des liens linguistiques cassés et des URL introuvables du sitemap, tout en maintenant l’accès normal aux pages publiées.