# General Translation Knowledge Base: Comment créer un plan de site pour un site web multilingue ?
URL: https://generaltranslation.com/fr/kb/multilingual-sitemap.mdx
Docs index: https://generaltranslation.com/llms.txt
Description: Répertoriez chaque version linguistique de chaque page dans sa propre entrée url. Si le plan de site porte vos annotations hreflang, attribuez à chaque entrée des variantes localisées xhtml:link pour toutes les versions, y compris elle-même, ainsi qu’un x-default facultatif, et scindez les fichiers au-delà de 50 000 URL ou 50 Mo. gt-next de General Translation attribue à chaque paramètre régional sa propre URL, que le plan de site peut ensuite répertorier.
Category: Référencement multilingue
Last updated: 2026-10-28
Authors: General Translation Team
Published: 2026-10-28

Un plan de site multilingue répertorie chaque version linguistique d’une page dans une entrée `<url>` distincte : `/en/pricing`, `/de/preise` et `/pt-BR/precos` constituent donc trois entrées. Si le plan de site déclare également les annotations hreflang, chaque entrée répertorie toutes les versions de la page, y compris elle-même, sous forme de variantes localisées `xhtml:link`. Google traite les annotations hreflang d’un plan de site exactement comme celles du HTML : le plan de site peut donc remplacer les balises de vos pages, à condition d’être complet et cohérent avec vos URL canoniques.

## Faut-il placer hreflang dans le plan de site ou dans votre HTML ?

Le [guide des versions localisées](https://developers.google.com/search/docs/specialty/international/localized-versions) de Google décrit trois méthodes équivalentes pour déclarer les versions linguistiques et précise que les combiner n&#39;apporte aucun avantage dans la recherche Google. Chaque URL linguistique que vous souhaitez faire indexer doit malgré tout figurer dans le plan de site ; le choix porte uniquement sur l&#39;emplacement des annotations hreflang.

| Méthode                           | Emplacement                       | Adaptée lorsque                                                                                                                              | Points de vigilance                                               |
| --------------------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| Balises HTML `<link>`             | Le `<head>` de chaque page        | Votre framework génère les métadonnées page par page, comme `alternates.languages` dans Next.js                                              | Chaque modèle doit émettre l&#39;ensemble complet des balises     |
| En-tête HTTP `Link`               | En-têtes de réponse               | Fichiers non HTML, comme les PDF                                                                                                             | Configuration du serveur pour chaque type de fichier              |
| `xhtml:link` dans le plan de site | Un seul plan de site ou plusieurs | Un CMS ou une étape de compilation connaît déjà chaque page et chaque paramètre régional, ou vous ne pouvez pas modifier les modèles de page | Les pages absentes du plan de site ne reçoivent aucune annotation |

Utilisez les balises HTML si votre framework les génère déjà page par page, et le plan de site si un point unique de votre compilation connaît chaque page et ses paramètres régionaux. Évitez de maintenir les deux à la main : deux listes finissent toujours par diverger. (Consultez [qu&#39;est-ce que hreflang](/kb/what-is-hreflang) pour la syntaxe des balises et l&#39;en-tête HTTP.)

## Comment structurer un plan de site multilingue

Ces règles proviennent du [guide des versions localisées](https://developers.google.com/search/docs/specialty/international/localized-versions) de Google, de son [guide sur les plans de site](https://developers.google.com/search/docs/crawling-indexing/sitemaps/build-sitemap) et du [protocole sitemaps](https://www.sitemaps.org/protocol.html) :

* **Un `<url>` par version.** Une page en trois langues correspond à trois entrées, chacune avec son propre `<loc>`.
* **Chaque entrée répertorie toutes les versions, y compris elle-même.** L’ensemble des `xhtml:link` est identique dans toutes les entrées d’une page, et Google ignore deux pages qui ne se référencent pas mutuellement.
* **`x-default` est facultatif.** Ajoutez-le pour un sélecteur de langue ou pour une version par défaut destinée aux visiteurs dont vous ne proposez pas la langue.
* **Déclarez l’espace de noms.** L’élément `<urlset>` doit comporter un attribut `xmlns:xhtml` défini sur `http://www.w3.org/1999/xhtml`.
* **Utilisez des URL finales et canoniques.** Elles doivent être absolues (`https://example.com/de/preise`, et non `/de/preise`), renvoyer un code `200` et ne pas être marquées `noindex`.
* **Utilisez des codes valides.** Une langue ISO 639-1, éventuellement accompagnée d’une région ISO 3166-1 alpha-2 (`en-GB`) ou d’un système d’écriture ISO 15924 (`zh-Hant`).
* **Échappez et encodez.** Le fichier est en UTF-8, `&` s’écrit `&amp;` et les chemins non ASCII sont encodés en pourcentage : `/de/über-uns` devient ainsi `/de/%C3%BCber-uns`.
* **Un `lastmod` par version.** Google ne l’utilise que s’il est systématiquement exact, c’est-à-dire s’il correspond à la date de la dernière modification significative de cette URL. Une traduction allemande mise à jour un autre jour que la page anglaise doit avoir sa propre date.

Chaque fichier peut contenir au maximum 50 000 URL et peser 50 Mo non compressé. Les éléments enfants `xhtml:link` ne comptent pas dans la limite d’URL, mais ils entrent dans la taille du fichier. Avec des URL d’environ 45 caractères, chaque variante localisée pèse environ 100 octets, et une entrée comportant 20 langues environ 2,2 Ko. Selon notre estimation, un fichier atteint 50 Mo vers 23 700 entrées de ce type, bien avant la limite de 50 000.

### Répartir par paramètre régional avec un index de plans de site

Un fichier par paramètre régional suffit à limiter la taille des fichiers pour la plupart des sites, mais ne garantit pas de rester sous les limites. D&#39;après l&#39;estimation ci-dessus, un paramètre régional comptant plus de 23 700 pages environ en 20 langues nécessite plusieurs fichiers : divisez-le alors davantage, par exemple en `sitemap-de-1.xml` et `sitemap-de-2.xml`. Il suffit de soumettre l&#39;index pour que Google lise tous les fichiers qu&#39;il répertorie ; soumettre aussi chaque fichier est facultatif, mais permet à chacun d&#39;avoir sa propre ligne dans le rapport Sitemaps de la Search Console. Un [index de plans de site](https://developers.google.com/search/docs/crawling-indexing/sitemaps/large-sitemaps) répertorie les fichiers :

```xml title="sitemap.xml"
<?xml version="1.0" encoding="UTF-8"?>
<sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <sitemap>
    <loc>https://example.com/sitemap-en.xml</loc>
  </sitemap>
  <sitemap>
    <loc>https://example.com/sitemap-de.xml</loc>
  </sitemap>
  <sitemap>
    <loc>https://example.com/sitemap-pt-br.xml</loc>
  </sitemap>
</sitemapindex>
```

Chaque fichier de paramètre régional ne répertorie que ses propres URL, mais chaque entrée contient toujours l&#39;ensemble des variantes localisées de la page :

```xml title="sitemap-de.xml"
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <url>
    <loc>https://example.com/de/preise</loc>
    <lastmod>2026-10-03</lastmod>
    <xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/pricing"/>
    <xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/preise"/>
    <xhtml:link rel="alternate" hreflang="pt-BR" href="https://example.com/pt-BR/precos"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en/pricing"/>
  </url>
  <url>
    <loc>https://example.com/de/ueber-uns</loc>
    <lastmod>2026-09-12</lastmod>
    <xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/about"/>
    <xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/ueber-uns"/>
    <xhtml:link rel="alternate" hreflang="pt-BR" href="https://example.com/pt-BR/sobre"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en/about"/>
  </url>
</urlset>
```

`sitemap-en.xml` et `sitemap-pt-br.xml` ont la même structure, chacun avec ses propres valeurs `<loc>` et `lastmod`. Placez ces fichiers à la racine du site : d’après Google, un plan de site ne couvre que les URL situées dans son propre répertoire ou en dessous, sauf si vous le soumettez dans la Search Console. De même, un index ne peut faire référence qu’à des plans de site situés dans son propre répertoire ou dans ses sous-répertoires.

## Comment en générer un dans Next.js

À partir de la version 14.2, Next.js génère les variantes localisées `xhtml:link` à partir du champ `alternates.languages` de chaque entrée de [`app/sitemap.ts`](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/sitemap#generate-a-localized-sitemap). Cette implémentation construit les variantes localisées à partir d&#39;une seule liste de paramètres régionaux et de slugs localisés : chaque entrée inclut donc sa propre URL ainsi que `x-default`. Le fichier `i18n/routing.ts` contient ces listes et indique quelles pages sont servies pour chaque paramètre régional ; [la section consacrée à General Translation](#how-general-translation-fits) en présente une version qui lit `gt.config.json`.

```ts title="app/sitemap.ts"
import type { MetadataRoute } from 'next';
import {
  BASE_URL,
  DEFAULT_LOCALE,
  LOCALES,
  localeRoutes,
  pathConfig,
} from '@/i18n/routing';

// Chemins partagés des pages à indexer.
const PAGES = ['/', '/about', '/pricing'];

// Un paramètre régional dessert une page, sauf si localeRoutes l'exclut.
const serves = (locale: string, page: string) =>
  !localeRoutes[locale] || localeRoutes[locale].includes(page);

// L'URL publique d'une page pour un paramètre régional donné, avec son slug localisé le cas échéant.
function localeUrl(locale: string, page: string) {
  const path = pathConfig[page]?.[locale] ?? page;
  return `${BASE_URL}/${locale}${path === '/' ? '' : path}`;
}

export default function sitemap(): MetadataRoute.Sitemap {
  return PAGES.flatMap((page) => {
    const locales = LOCALES.filter((locale) => serves(locale, page));

    // Toutes les versions de la page, y compris celle de <loc>, ainsi que x-default.
    const languages: Record<string, string> = Object.fromEntries(
      locales.map((locale) => [locale, localeUrl(locale, page)]),
    );
    languages['x-default'] = localeUrl(DEFAULT_LOCALE, page);

    // Un <url> par version, chacun avec les mêmes variantes localisées.
    return locales.map((locale) => ({
      url: localeUrl(locale, page),
      alternates: { languages },
    }));
  });
}
```

Avec Next.js 16.4.3 et `gt-next` 11.4.16, la page `/about` a généré quatre entrées semblables à celle-ci, chacune avec les cinq mêmes liens :

```xml title="/sitemap.xml"
<url>
<loc>https://example.com/en/about</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/ueber-uns" />
<xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr/qui-sommes-nous" />
<xhtml:link rel="alternate" hreflang="pt-BR" href="https://example.com/pt-BR/about" />
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en/about" />
</url>
```

Trois points que la documentation de Next.js ne précise pas :

* **Ajoutez l&#39;autoréférence.** Dans l&#39;exemple localisé de la documentation de Next.js, seules les variantes localisées `es` et `de` figurent sous l&#39;URL anglaise, et les URL espagnole et allemande n&#39;ont aucune entrée propre. Or Google attend que chaque entrée se référence elle-même et que chaque version dispose de son propre `<url>`.
* **Échappez vous-même les URL.** Lors de notre test, Next.js 16.4.3 a inséré les URL telles quelles dans le XML : un `&` dans une chaîne de requête a produit un fichier rejeté par `xmllint`, et le `ü` n&#39;a pas été encodé. Évitez les chaînes de requête dans les URL du plan de site et construisez les chemins avec `new URL(path, BASE_URL).href`, qui applique l&#39;encodage-pourcent.
* **Le découpage ne crée pas d&#39;index.** [`generateSitemaps`](https://nextjs.org/docs/app/api-reference/functions/generate-sitemaps) peut renvoyer un id par paramètre régional, et Next.js sert alors chaque fichier à `/sitemap/en.xml`, `/sitemap/de.xml`, etc. Lors de notre test, `/sitemap.xml` renvoyait ensuite une erreur 404 : soumettez donc chaque fichier dans la Search Console, ce qui lève au passage la restriction de répertoire imposée par Google aux fichiers situés dans `/sitemap/`.

## Erreurs courantes

* **Auto-références ou liens de retour manquants.** Si `/en/pricing` référence `/de/preise` mais que `/de/preise` ne référence pas `/en/pricing`, Google ignore la paire. Cela se produit généralement lorsqu&#39;un paramètre régional est ajouté à certaines pages seulement.
* **Codes invalides.** `en-UK` n&#39;a aucun effet, car `UK` est un code réservé ; utilisez `en-GB`. `es-419` est un code BCP 47 valide, mais il n&#39;est pas pris en charge par hreflang ; utilisez `es` ou des codes de pays précis comme `es-MX`.
* **URL qui redirigent.** Indiquez l&#39;URL pour laquelle votre serveur répond avec un code `200`. Avec les slugs de l&#39;exemple ci-dessus, `/de/about` redirige vers `/de/ueber-uns` : seul le slug localisé a donc sa place dans le plan de site.
* **Pages `noindex` ou non traduites.** Un plan de site recense les URL que vous souhaitez voir apparaître dans les résultats de recherche. Omettez tout paramètre régional qui n&#39;est pas encore publié, et retirez-le également des variantes localisées des autres entrées.
* **`lastmod` défini sur la date du build.** Régénérer le plan de site ne constitue pas une modification du contenu. Utilisez la date de dernière modification de chaque version, ou omettez ce champ.

## Comment le vérifier avant de le soumettre

Effectuez trois vérifications avant de soumettre le plan de site via le rapport Sitemaps de la Search Console ou d’ajouter une ligne `Sitemap:` au fichier `robots.txt` :

1. **Le XML est bien formé :** `xmllint --noout sitemap-de.xml`.
2. **Chaque groupe est complet et réciproque.** Ce script lit un ou plusieurs fichiers de plan de site et signale les entrées qui ne se référencent pas elles-mêmes, les variantes localisées dépourvues de leur propre entrée, les ensembles incohérents et les codes mal formés :

   ```python title="check_sitemap.py"
   import re
   import sys
   import xml.etree.ElementTree as ET

   NS = {
       's': 'http://www.sitemaps.org/schemas/sitemap/0.9',
       'x': 'http://www.w3.org/1999/xhtml',
   }
   # Langue, système d’écriture facultatif, région facultative : détecte es-419, mais pas tous les codes invalides.
   CODE = re.compile(r'^[a-z]{2}(-[a-z]{4})?(-[a-z]{2})?$', re.IGNORECASE)
   RESERVED = ('-uk', '-eu', '-un')

   # Lit chaque <url> de chaque fichier de plan de site : loc -> {hreflang: href}.
   entries = {}
   for path in sys.argv[1:]:
       for url in ET.parse(path).getroot().findall('s:url', NS):
           loc = url.findtext('s:loc', namespaces=NS).strip()
           entries[loc] = {
               link.get('hreflang'): link.get('href')
               for link in url.findall('x:link', NS)
               if link.get('rel') == 'alternate'
           }

   problems = []
   for loc, alternates in entries.items():
       if alternates and loc not in alternates.values():
           problems.append(f'{loc} does not list itself')
       for lang, href in alternates.items():
           if lang == 'x-default':
               continue
           if not CODE.match(lang) or lang.lower().endswith(RESERVED):
               problems.append(f'{loc} uses an invalid hreflang code: {lang}')
           if href not in entries:
               problems.append(f'{loc} lists {href}, which has no <url> entry')
           elif entries[href] != alternates:
               problems.append(f'{href} does not list the same alternates as {loc}')

   print('\n'.join(problems) or f'{len(entries)} URLs, no problems found')
   sys.exit(1 if problems else 0)
   ```

   Exécutez-le en une seule fois sur l’ensemble des fichiers de paramètres régionaux : `python3 check_sitemap.py sitemap-en.xml sitemap-de.xml sitemap-pt-br.xml`. Pour l’exemple ci-dessus, il affiche `6 URLs, no problems found`.
3. **Chaque URL renvoie `200` :**

   ```bash
   curl -s https://example.com/sitemap-de.xml | grep -o '<loc>[^<]*' | sed 's/<loc>//' |
     while read -r url; do echo "$(curl -s -o /dev/null -w '%{http_code}' "$url") $url"; done
   ```

## Le rôle de General Translation

Le package `gt-next` de General Translation attribue à chaque paramètre régional sa propre URL, et ce sont précisément ces URL que répertorie un plan de site multilingue. [`createNextMiddleware`](/docs/react/nextjs/reference/functions/create-next-middleware) sert les chemins `/de/...` et `/pt-BR/...`, `prefixDefaultLocale: true` place aussi la langue source sous `/en/`, [`pathConfig`](/docs/react/nextjs/reference/functions/create-next-middleware#path-config) associe une page à un slug localisé, et [`localeRoutes`](/docs/react/nextjs/reference/functions/create-next-middleware#locale-routes) limite les pages servies pour un paramètre régional donné. Regroupez ces paramètres dans un seul module et importez-le à la fois dans le middleware et dans le plan de site : ce dernier répertoriera ainsi exactement les URL auxquelles répond le middleware :

```ts title="i18n/routing.ts"
import gtConfig from '../gt.config.json';

export const BASE_URL = 'https://example.com';
export const DEFAULT_LOCALE = gtConfig.defaultLocale;

// Tous les paramètres régionaux pris en charge, issus de gt.config.json, en commençant par la langue source.
export const LOCALES = [...new Set([gtConfig.defaultLocale, ...gtConfig.locales])];

// Partagé avec proxy.ts, pour que le plan de site liste exactement les URL servies par le middleware.
export const pathConfig: Record<string, Record<string, string>> = {
  '/about': { de: '/ueber-uns', fr: '/qui-sommes-nous' },
};
export const localeRoutes: Record<string, string[]> = {
  'pt-BR': ['/', '/about'], // la page des tarifs n'est pas encore traduite en portugais brésilien
};
```

```ts title="proxy.ts"
import { createNextMiddleware } from 'gt-next/middleware';
import { localeRoutes, pathConfig } from './i18n/routing';

export default createNextMiddleware({
  prefixDefaultLocale: true,
  pathConfig,
  localeRoutes,
});

export const config = {
  matcher: ['/((?!api|static|.*\\..*|_next).*)'],
};
```

Avec un fichier `gt.config.json` qui liste `en`, `de`, `fr` et `pt-BR`, la compilation a produit 11 entrées : trois pages dans quatre paramètres régionaux, moins la page de tarifs en portugais brésilien. Cette URL reste accessible aux visiteurs, car le middleware redirige `/pt-BR/pricing` vers `/en/pricing` avec un code `307`, et une URL qui redirige n&#39;a pas sa place dans un plan de site. Toutes les URL listées ont renvoyé `200`, et le vérificateur ci-dessus n&#39;a détecté aucun problème. La vérification `serves` compare des chemins exacts : si vos `localeRoutes` utilisent des motifs tels que `/blog/[[...slug]]`, remplacez-les par les pages concrètes que vous listez.

Si vous routez un paramètre régional sous un alias, comme `/cn` pour le chinois, utilisez le code canonique dans la clé hreflang et l&#39;alias dans l&#39;URL ; le [guide SEO des alias de paramètres régionaux](/docs/react/nextjs/locale-alias-seo#sitemap) effectue la conversion avec [`resolveCanonicalLocale`](/docs/react/nextjs/reference/functions/resolve-canonical-locale). La traduction des titres et des descriptions de ces mêmes pages est abordée dans [Comment traduire les titres SEO et les méta-descriptions ?](/kb/translate-seo-metadata).

## Points clés

* Attribuez à chaque version linguistique sa propre entrée `<url>`, et répertoriez toutes les URL que vous souhaitez faire indexer, quelle que soit la méthode hreflang utilisée.
* Dans un plan de site avec hreflang, chaque entrée répertorie toutes les versions de la page, y compris elle-même, et cet ensemble est identique d&#39;une entrée à l&#39;autre ; `x-default` est facultatif.
* Les fichiers sont limités à 50 000 URL et 50 Mo ; les variantes localisées ne comptent pas dans la limite d&#39;URL, mais elles comptent dans la taille : avec de nombreux paramètres régionaux, c&#39;est donc la limite de taille qui est atteinte en premier.
* Découpez par paramètre régional, puis subdivisez les paramètres régionaux les plus volumineux, le tout sous un index de plans de site à la racine du site ; soumettez l&#39;index après avoir vérifié la réciprocité et les codes d&#39;état.
* Avec `gt-next`, partagez `pathConfig` et `localeRoutes` entre le middleware et `app/sitemap.ts`, afin que le plan de site répertorie exactement les URL servies par votre application.

## Related questions

- [Qu’est-ce que hreflang et comment le configurer ?](https://generaltranslation.com/fr/kb/what-is-hreflang.mdx): Hreflang indique à Google quelles URL correspondent aux versions linguistiques ou régionales d’une même page, afin qu’il affiche la bonne dans les résultats de recherche. Ajoutez-le à l’aide de balises link HTML, d’en-têtes HTTP Link ou d’un plan de site, veillez à ce que chaque lien soit réciproque et utilisez des codes valides comme en-GB, et non en-UK.
- [Comment configurer le SEO d’une application Next.js multilingue ?](https://generaltranslation.com/fr/kb/multilingual-nextjs-seo.mdx): Attribuez à chaque langue sa propre URL préfixée, comme /en/about et /es/about, puis publiez html lang, une URL canonique autoréférente, des variantes localisées hreflang avec x-default, un titre et une description traduits, ainsi qu’une entrée de plan de site pour chaque langue publiée. Marquez les versions non publiées en noindex. Le package gt-next de General Translation associe chaque langue à sa propre URL et traduit les titres avec getGT.
- [Redirections linguistiques et SEO : URL, cookie ou Accept-Language ?](https://generaltranslation.com/fr/kb/locale-routing-seo.mdx): Laissez l’URL déterminer la langue de chaque page que vous souhaitez faire indexer, car Googlebot n’envoie aucun en-tête Accept-Language et explore principalement depuis des adresses américaines. Ne vous servez du choix enregistré dans un cookie, puis d’Accept-Language, que pour décider vers où redirige une racine neutre comme /, avec une redirection temporaire 302 ou 307.
- [Sous-répertoire, sous-domaine ou ccTLD pour le SEO multilingue](https://generaltranslation.com/fr/kb/multilingual-url-structure.mdx): Utilisez des sous-répertoires comme example.com/de/ lorsqu’un même produit partage un site et un processus de livraison. Seul un ccTLD signale un pays, les sous-domaines permettent un hébergement distinct, Google déconseille les paramètres ?lang= et considère les domaines « de complaisance » comme .io et .ai comme génériques.
- [Comment traduire les titres SEO et les méta-descriptions ?](https://generaltranslation.com/fr/kb/translate-seo-metadata.mdx): Rédigez le titre et la méta-description de chaque version linguistique dans la langue concernée, avec les termes réellement recherchés sur ce marché, qui diffèrent souvent d’une traduction littérale. Localisez ensuite les balises associées, notamment Open Graph, canonique, hreflang, html lang et les données structurées. Avec gt-next de General Translation, les titres et les descriptions sont traduits dans generateMetadata grâce à getGT.

## Previous and next questions

- Previous question: [Comment ajouter l’i18n à une application Next.js utilisant le Pages Router ?](https://generaltranslation.com/fr/kb/add-i18n-to-nextjs-pages-router.mdx)
- Next question: [Comment garantir une terminologie cohérente dans la traduction par IA ?](https://generaltranslation.com/fr/kb/ai-translation-glossary.mdx)

## Sitemap

See the [Knowledge Base sitemap](https://generaltranslation.com/fr/kb/sitemap.md) for every article.
