Changer le thème

SEO multilingue Next.js : guide complet pour un indexage correct de chaque langue

Easton editorial illustration: island architecture model

Vous avez soigneusement construit un site Web multilingue - et pourtant la recherche affiche la mauvaise version linguistique. Les utilisateurs effectuent des recherches en chinois mais se retrouvent sur la page en anglais. Les différentes versions linguistiques se font concurrence dans les résultats de recherche et dégradent le classement général.

Symptômes typiques d’un référencement multilingue mal configuré. Selon les statistiques de Google, plus de 60 % des sites Web multilingues présentent des erreurs de configuration hreflang - avec un impact notable sur l’internationalisation et l’expérience utilisateur.

60%+
Taux d’erreur sur les sites multilingues
Plus de 60 % des sites multilingues ont des erreurs de configuration hreflang

Cet article montre comment implémenter correctement le référencement multilingue dans Next.js :

  • configurer correctement les balises hreflang – ne confondez pas les versions linguistiques
  • Générer des plans de site multilingues – accélérez l’indexation
  • Bonnes pratiques en matière de structure d’URL : choisissez la stratégie d’internationalisation optimale
  • Trouver et corriger les erreurs typiques - localiser rapidement les problèmes

Qu’il s’agisse de Pages Router ou d’App Router, vous trouverez des solutions adaptées aux deux.

1. Concepts de base du référencement multilingue

1.1 Qu’est-ce que le hreflang ?

hreflang est un attribut HTML qui indique aux moteurs de recherche la langue cible et la région d’une page. Fonctions principales :

  1. Évitez les pénalités pour contenu en double – Les moteurs de recherche le reconnaissent : les différentes versions linguistiques sont des traductions du même contenu, et non du contenu en double.
  2. Faire correspondre précisément les utilisateurs - version linguistique appropriée en fonction de la langue et de la région de l’utilisateur
  3. Améliorez l’UX – Les utilisateurs ne voient pas la mauvaise langue

1.2 Comment Google traite le contenu multilingue

Lors de l’exploration d’un site Web multilingue, Google fait ceci :

  1. Détecter le langage (HTML-lang, hreflang, analyse de contenu)
  2. Évaluez les balises hreflang et comprenez les relations entre les versions linguistiques
  3. Afficher la version correspondante dans les résultats de recherche
  4. Regroupez les signaux SEO des versions linguistiques (au lieu de les laisser se concurrencer)

1.3 Erreurs SEO typiques

Erreur 1 : balises hreflang manquantes

<!-- ❌ Erreur : pas de balises hreflang -->
<head>
  <title>My Website</title>
  <link rel="canonical" href="https://example.com/en/about" />
</head>

Conséquence : Les moteurs de recherche ne reconnaissent pas les relations linguistiques : des versions linguistiques incorrectes peuvent apparaître dans les résultats.

Erreur 2 : configuration hreflang asymétrique

<!-- Page en anglais -->
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />

<!-- ❌ Page en chinois — erreur : hreflang manquant -->
<!-- Chaque version linguistique doit avoir des balises hreflang complètes -->

Conséquence : Google exige une configuration hreflang symétrique : les informations unilatérales sont ignorées.

Erreur 3 : codes de langue incorrects

<!-- ❌ Erreur : codes de langue invalides -->
<link rel="alternate" hreflang="cn" href="..." /> <!-- correct : zh -->
<link rel="alternate" hreflang="en-us" href="..." /> <!-- correct : en-US, respecter la casse -->

Conséquence : Les moteurs de recherche ne reconnaissent pas les codes - le hreflang ne fonctionne pas.## 2. Choisissez la stratégie d’URL

Déterminez une stratégie d’URL appropriée avant la mise en œuvre - elle influence le référencement, l’UX et la mise en œuvre technique.

2.1 Comparaison de trois stratégies d’URL courantes

StratégieExempleImpact SEOMise en œuvreRecommandation
Sous-répertoireexemple.com/en/
exemple.com/zh/
⭐⭐⭐⭐⭐ Meilleur⭐⭐⭐ Moyen⭐⭐⭐⭐⭐
Sous-domainefr.example.com
zh.example.com
⭐⭐⭐ Moyen⭐⭐⭐⭐ Complexe⭐⭐⭐
Paramètres d’URLexemple.com?lang=fr⭐⭐ Faible⭐⭐⭐⭐⭐ Facile⭐⭐

2.2 Les stratégies en détail

Option 1 : Sous-répertoire (recommandé)

Avantages :

  • Les signaux SEO sont concentrés sur le domaine principal – meilleur classement global
  • Configuration facile, pas de domaines supplémentaires ni de certificats SSL
  • Déploiement maintenable, évolutif et cohérent
  • Prise en charge native de Next.js

Inconvénients :

  • Toutes les langues partagent un domaine - pas de réglage DNS par marché

Implémentation de Next.js :

// next.config.js
module.exports = {
  i18n: {
    locales: ['en', 'zh', 'ja', 'de'],
    defaultLocale: 'en',
    localeDetection: true // détection automatique de la langue utilisateur
  }
}

Option 2 : Sous-domaine

Avantages :

  • Serveur séparé possible par marché (par exemple Chine séparément)
  • Pile technologique indépendante par sous-domaine
  • CDN et optimisation géographique plus faciles

Inconvénients :

  • Les signaux SEO sont distribués – chaque sous-domaine a besoin de son propre classement
  • Gestion de domaine supplémentaire et SSL
  • Coûts de mise en œuvre et de maintenance plus élevés

Option 3 : paramètres d’URL (non recommandé)

Avantages :

  • Le plus simple à mettre en œuvre

Inconvénients :

  • Pire effet SEO – les paramètres sont souvent ignorés
  • Mauvaise UX, URL peu descriptives
  • Mise en cache CDN difficile
  • Versions linguistiques difficiles à distinguer dans la recherche

Conclusion :

Pour la plupart des projets : Stratégie de sous-répertoire. Meilleur équilibre entre référencement, effort et maintenance.## 3. hreflang en détail

3.1 Abandon des balises hreflang

hreflang indique aux moteurs de recherche :

  1. quelles versions linguistiques sont disponibles
  2. l’URL complète de chaque version
  3. Code de langue et de région par version

3.2 hreflang dans le routeur d’application Next.js

Méthode 1 : API de métadonnées (recommandée)

Routeur d’applications Next.js 13+ avec API de métadonnées légère :

// app/[lang]/about/page.tsx
import { Metadata } from 'next'

type Props = {
  params: { lang: string }
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { lang } = params

  // Langues prises en charge
  const languages = ['en', 'zh', 'ja', 'de']

  // Liens alternate pour toutes les langues
  const alternates = {
    canonical: `https://example.com/${lang}/about`,
    languages: languages.reduce((acc, locale) => {
      acc[locale] = `https://example.com/${locale}/about`
      return acc
    }, {} as Record<string, string>)
  }

  return {
    title: 'About Us',
    alternates,
    // x-default pour les langues non correspondantes
    other: {
      'x-default': 'https://example.com/en/about'
    }
  }
}

export default function AboutPage({ params }: Props) {
  return <div>About page in {params.lang}</div>
}

Méthode 2 : composant de tête propre

Pour plus de contrôle :

// components/I18nHead.tsx
import Head from 'next/head'

interface I18nHeadProps {
  currentLang: string
  pathname: string
  languages?: string[]
}

export default function I18nHead({
  currentLang,
  pathname,
  languages = ['en', 'zh', 'ja', 'de']
}: I18nHeadProps) {
  const baseUrl = 'https://example.com'

  return (
    <Head>
      {/* Canonical de la page courante */}
      <link rel="canonical" href={`${baseUrl}/${currentLang}${pathname}`} />

      {/* hreflang pour toutes les versions linguistiques */}
      {languages.map(lang => (
        <link
          key={lang}
          rel="alternate"
          hrefLang={lang}
          href={`${baseUrl}/${lang}${pathname}`}
        />
      ))}

      {/* x-default vers la langue par défaut */}
      <link
        rel="alternate"
        hrefLang="x-default"
        href={`${baseUrl}/en${pathname}`}
      />
    </Head>
  )
}

Utilisation :

// app/[lang]/about/page.tsx
import I18nHead from '@/components/I18nHead'

export default function AboutPage({ params }: { params: { lang: string } }) {
  return (
    <>
      <I18nHead
        currentLang={params.lang}
        pathname="/about"
      />
      <div>About page content</div>
    </>
  )
}

3.3 hreflang dans le routeur de pages Next.js

Pages Router utilise d’autres API :

// pages/about.tsx
import { GetStaticProps } from 'next'
import Head from 'next/head'
import { useRouter } from 'next/router'

export default function AboutPage() {
  const router = useRouter()
  const { locale, locales, asPath } = router
  const baseUrl = 'https://example.com'

  return (
    <>
      <Head>
        {/* Canonical de la page courante */}
        <link rel="canonical" href={`${baseUrl}/${locale}${asPath}`} />

        {/* hreflang pour toutes les versions linguistiques */}
        {locales?.map(loc => (
          <link
            key={loc}
            rel="alternate"
            hrefLang={loc}
            href={`${baseUrl}/${loc}${asPath}`}
          />
        ))}

        {/* x-default langue par défaut */}
        <link
          rel="alternate"
          hrefLang="x-default"
          href={`${baseUrl}/en${asPath}`}
        />
      </Head>

      <div>About page content</div>
    </>
  )
}

export const getStaticProps: GetStaticProps = async ({ locale }) => {
  return {
    props: {
      messages: (await import(`../locales/${locale}.json`)).default
    }
  }
}

3.4 Codes régionaux (langue-REGION)

Pour le contenu spécifique à un pays, le format « langue-REGION » :

// Anglais pour différentes régions
const hreflangConfig = {
  'en-US': 'https://example.com/en-us/about', // US-Englisch
  'en-GB': 'https://example.com/en-gb/about', // UK-Englisch
  'en-AU': 'https://example.com/en-au/about', // Australien
  'zh-CN': 'https://example.com/zh-cn/about', // Festlandchina vereinfachtes Chinesisch
  'zh-TW': 'https://example.com/zh-tw/about', // Taiwan traditionelles Chinesisch
  'zh-HK': 'https://example.com/zh-hk/about', // Hongkong traditionelles Chinesisch
}

Routage régional dans Next.js :

// next.config.js
module.exports = {
  i18n: {
    locales: ['en-US', 'en-GB', 'en-AU', 'zh-CN', 'zh-TW', 'zh-HK'],
    defaultLocale: 'en-US',
  }
}

3.5 Erreurs de configuration typiques et correctifs

Erreur 1 : Manque d’auto-référence

<!-- ❌ Erreur : la page courante ne se référence pas elle-même -->
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />

<!-- ✅ Correct : auto-référence obligatoire -->
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />

Pourquoi l’auto-référence ?
Google exige un hreflang symétrique : chaque version linguistique doit faire référence à toutes les autres, y compris elle-même.

Erreur 2 : x-default manquant

<!-- ✅ Recommandé : x-default comme repli -->
<link rel="alternate" hreflang="x-default" href="https://example.com/en/about" />

x-default renvoie la version par défaut pour les langues non prises en charge, par ex. Par exemple :

  • Utilisateurs avec une langue de navigateur arabe, site Web sans arabe
  • Le moteur de recherche renvoie la page spécifiée sous x-default

Erreur 3 : hreflang et canonique en conflit

<!-- ❌ Erreur : canonical pointe vers une autre langue -->
<link rel="canonical" href="https://example.com/en/about" />
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />

<!-- ✅ Correct : canonical pointe vers la version linguistique courante -->
<link rel="canonical" href="https://example.com/zh/about" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />

Règle de base : canonique doit être l’URL de la page actuelle - jamais une version linguistique différente.## 4. Plan du site multilingue

Les plans de site aident les moteurs de recherche à trouver et à indexer les pages. Pour les sites multilingues, une configuration appropriée du plan du site est cruciale.

4.1 Pourquoi un plan de site multilingue ?

Trois avantages :

  1. Indexation plus rapide – signalez activement toutes les versions linguistiques au lieu d’attendre le robot d’exploration
  2. Exhaustivité – aucune version linguistique n’est négligée, en particulier avec les structures de liens profonds
  3. hreflang dans le plan du site – renforce encore les relations linguistiques

4.2 Stratégie de plan de site

Selon la taille du site :

Option 1 : Un plan du site (petits sites)

Toutes les URL de langues dans un seul sitemap.xml – adapté pour jusqu’à env. 5 000 pages :

<?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">
  <!-- Version anglaise -->
  <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="zh" href="https://example.com/zh/about"/>
    <xhtml:link rel="alternate" hreflang="ja" href="https://example.com/ja/about"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en/about"/>
  </url>
  <!-- Version chinoise -->
  <url>
    <loc>https://example.com/zh/about</loc>
    <xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/about"/>
    <xhtml:link rel="alternate" hreflang="zh" href="https://example.com/zh/about"/>
    <xhtml:link rel="alternate" hreflang="ja" href="https://example.com/ja/about"/>
    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en/about"/>
  </url>
</urlset>

Option 2 : Plan du site par langue (grands sites)

Un plan de site par langue, regroupé à l’aide d’un index de plan de site – à partir d’environ 5 000 pages ou de nombreuses langues :

<!-- sitemap-index.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>
    <lastmod>2025-01-01</lastmod>
  </sitemap>
  <sitemap>
    <loc>https://example.com/sitemap-zh.xml</loc>
    <lastmod>2025-01-01</lastmod>
  </sitemap>
  <sitemap>
    <loc>https://example.com/sitemap-ja.xml</loc>
    <lastmod>2025-01-01</lastmod>
  </sitemap>
</sitemapindex>

4.3 Plan du site dans le routeur d’application Next.js

Next.js 13+ avec génération de plan de site intégrée :

// app/sitemap.ts
import { MetadataRoute } from 'next'

const languages = ['en', 'zh', 'ja', 'de']
const routes = ['', '/about', '/blog', '/contact']

export default function sitemap(): MetadataRoute.Sitemap {
  const baseUrl = 'https://example.com'
  const sitemap: MetadataRoute.Sitemap = []

  routes.forEach(route => {
    languages.forEach(lang => {
      const url = `${baseUrl}/${lang}${route}`

      sitemap.push({
        url,
        lastModified: new Date(),
        changeFrequency: 'weekly',
        priority: route === '' ? 1 : 0.8,
        alternates: {
          languages: languages.reduce((acc, l) => {
            acc[l] = `${baseUrl}/${l}${route}`
            return acc
          }, {} as Record<string, string>)
        }
      })
    })
  })

  return sitemap
}

4.4 Plan du site pour le contenu dynamique

Pour les articles de blog ou les produits de DB/CMS :

// app/sitemap.ts
import { MetadataRoute } from 'next'

const languages = ['en', 'zh', 'ja']
const baseUrl = 'https://example.com'

async function getArticles() {
  return [
    { slug: 'getting-started', lastModified: '2025-01-01' },
    { slug: 'advanced-guide', lastModified: '2025-01-15' },
  ]
}

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const sitemap: MetadataRoute.Sitemap = []

  const staticPages = ['', '/about', '/contact']
  staticPages.forEach(page => {
    languages.forEach(lang => {
      sitemap.push({
        url: `${baseUrl}/${lang}${page}`,
        lastModified: new Date(),
        changeFrequency: 'monthly',
        priority: page === '' ? 1 : 0.8,
        alternates: {
          languages: languages.reduce((acc, l) => {
            acc[l] = `${baseUrl}/${l}${page}`
            return acc
          }, {} as Record<string, string>)
        }
      })
    })
  })

  const articles = await getArticles()
  articles.forEach(article => {
    languages.forEach(lang => {
      sitemap.push({
        url: `${baseUrl}/${lang}/blog/${article.slug}`,
        lastModified: new Date(article.lastModified),
        changeFrequency: 'weekly',
        priority: 0.6,
        alternates: {
          languages: languages.reduce((acc, l) => {
            acc[l] = `${baseUrl}/${l}/blog/${article.slug}`
            return acc
          }, {} as Record<string, string>)
        }
      })
    })
  })

  return sitemap
}

4.5 Plan du site dans Pages Router

Manuellement via la route API :

// pages/api/sitemap.xml.ts
import { NextApiRequest, NextApiResponse } from 'next'

const baseUrl = 'https://example.com'
const languages = ['en', 'zh', 'ja']

function generateSiteMap(pages: string[]) {
  return `<?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">
${pages.map(page => {
  return languages.map(lang => {
    const url = `${baseUrl}/${lang}${page}`
    const alternates = languages.map(l =>
      `    <xhtml:link rel="alternate" hreflang="${l}" href="${baseUrl}/${l}${page}"/>`
    ).join('\n')

    return `  <url>
    <loc>${url}</loc>
    <lastmod>${new Date().toISOString()}</lastmod>
    <changefreq>weekly</changefreq>
    <priority>0.8</priority>
${alternates}
    <xhtml:link rel="alternate" hreflang="x-default" href="${baseUrl}/en${page}"/>
  </url>`
  }).join('\n')
}).join('\n')}
</urlset>`
}

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  const pages = ['', '/about', '/blog', '/contact']
  const sitemap = generateSiteMap(pages)

  res.setHeader('Content-Type', 'text/xml')
  res.write(sitemap)
  res.end()
}

4.6 Soumettre le plan du site aux moteurs de recherche

Soumettre activement après génération :

Méthode 1 : Déclarer dans robots.txt

Variante la plus simple – les robots lisent automatiquement :

# public/robots.txt
User-agent: *
Allow: /

Sitemap: https://example.com/sitemap.xml

Méthode 2 : Google Search Console

La soumission manuelle accélère l’indexation :

  1. Ouvrez Google Search Console.
  2. Sélectionnez la propriété
  3. Menu Plans du site
  4. Entrez l’URL du sitemap.xml
  5. Cliquez sur « Envoyer ».

Méthode 3 : Outils Bing pour les webmasters

Bing détient une part de marché pertinente :

  1. Ouvrez Bing Webmaster Tools.
  2. Ajouter un site Web
  3. Soumettez le plan du site dans la section « Plans du site »

4.7 Valider le plan du site

Outils de validation :

  1. Validateur de plan de site XML : https://www.xml-sitemaps.com/validate-xml-sitemap.html
  2. Google Search Console : état de l’index et erreurs après la soumission
  3. Validateur XML en ligne : Vérifiez la conformité aux normes## 5. Meilleures pratiques et notes

5.1 Qualité des traductions

Google reconnaît les traductions de mauvaise qualité – cela affecte le classement.

Éviter :

  • ❌ Publier directement la traduction automatique (Google Translate)
  • ❌ Traduisez uniquement la navigation et le titre, laissez le corps dans une seule langue
  • ❌ Fortes différences de contenu entre les versions linguistiques

Recommandé :

  • ✅ Traducteurs professionnels ou locuteurs natifs
  • ✅ Localisation au lieu de traduction pure (culture, expression)
  • ✅ Structure et qualité uniformes de toutes les versions linguistiques

5.2 Risque SEO lié à la traduction automatique client

La traduction automatique côté client ne fonctionne pas pour le référencement : les robots d’exploration voient le contenu original :

// ❌ Non recommandé : traduction automatique côté client (non indexable)
import GoogleTranslate from 'google-translate-api'

export default function Page() {
  const [content, setContent] = useState('')

  useEffect(() => {
    GoogleTranslate(originalText, { to: 'zh' })
      .then(res => setContent(res.text))
  }, [])

  return <div>{content}</div>
}
// ✅ Recommandé : SSR avec de vraies traductions
export default function Page({ params }: { params: { lang: string } }) {
  const content = await getTranslatedContent(params.lang)

  return <div>{content}</div>
}

5.3 Performances

Les sites multilingues touchent des utilisateurs du monde entier – la performance compte.

1. CDN multirégional

// next.config.js
module.exports = {
  images: {
    domains: ['cdn.example.com'],
  },
  compress: true,
}

2. Chargement paresseux des packs de langue

Ne chargez pas tous les fichiers de traduction en même temps :

const messages = await import(`@/locales/${lang}.json`)

3. Stratégie de cache

// app/[lang]/layout.tsx
export const revalidate = 3600 // stündliche Revalidierung

5.4 Surveillance et maintenance

Le référencement multilingue n’est pas une tâche ponctuelle.

1. Vérifiez les erreurs hreflang

Rapport de ciblage international de la console de recherche Google :

  • erreurs et avertissements hreflang
  • Statut de l’index par langue
  • Performances de recherche et taux de clics par langue

2. Outils recommandés

3. Script de surveillance

Vérification automatisée du hreflang :

// scripts/check-hreflang.ts
import { JSDOM } from 'jsdom'

async function checkHreflang(url: string) {
  const response = await fetch(url)
  const html = await response.text()
  const dom = new JSDOM(html)
  const document = dom.window.document

  const hreflangLinks = document.querySelectorAll('link[rel="alternate"][hreflang]')

  console.log(`Found ${hreflangLinks.length} hreflang links on ${url}`)

  hreflangLinks.forEach(link => {
    const hreflang = link.getAttribute('hreflang')
    const href = link.getAttribute('href')
    console.log(`  ${hreflang}: ${href}`)
  })

  const currentUrl = new URL(url).href
  const hasSelfReference = Array.from(hreflangLinks).some(
    link => link.getAttribute('href') === currentUrl
  )

  if (!hasSelfReference) {
    console.warn('⚠️ Warning: Missing self-reference hreflang tag')
  }

  const hasXDefault = Array.from(hreflangLinks).some(
    link => link.getAttribute('hreflang') === 'x-default'
  )

  if (!hasXDefault) {
    console.warn('⚠️ Warning: Missing x-default hreflang tag')
  }
}

checkHreflang('https://example.com/en/about')
checkHreflang('https://example.com/zh/about')
```## 6. Exemple pratique : projet complet

Comment implémenter le référencement multilingue dans un projet Next.js App Router.

### 6.1 Structure du projet

my-i18n-site/
├── app/
│ ├── [lang]/
│ │ ├── layout.tsx
│ │ ├── page.tsx
│ │ ├── about/
│ │ │ └── page.tsx
│ │ └── blog/
│ │ ├── page.tsx
│ │ └── [slug]/
│ │ └── page.tsx
│ ├── sitemap.ts
│ └── robots.ts
├── components/
│ └── I18nMetadata.tsx
├── lib/
│ ├── i18n.ts
│ └── articles.ts
├── locales/
│ ├── en.json
│ ├── zh.json
│ └── ja.json
└── next.config.js


### 6.2 Configuration

```javascript
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  // App Router n'utilise pas i18n dans next.config — routage manuel
}

module.exports = nextConfig
// lib/i18n.ts
export const languages = ['en', 'zh', 'ja'] as const
export type Language = (typeof languages)[number]

export const defaultLanguage: Language = 'en'

export const languageNames: Record<Language, string> = {
  en: 'English',
  zh: '中文',
  ja: '日本語',
}

export function isValidLanguage(lang: string): lang is Language {
  return languages.includes(lang as Language)
}

6.3 Composant de mise en page

// app/[lang]/layout.tsx
import { languages, isValidLanguage } from '@/lib/i18n'
import { notFound } from 'next/navigation'

export async function generateStaticParams() {
  return languages.map(lang => ({ lang }))
}

export default function LangLayout({
  children,
  params,
}: {
  children: React.ReactNode
  params: { lang: string }
}) {
  if (!isValidLanguage(params.lang)) {
    notFound()
  }

  return (
    <html lang={params.lang}>
      <body>{children}</body>
    </html>
  )
}

6.4 Page Métadonnées

// app/[lang]/about/page.tsx
import { Metadata } from 'next'
import { languages, Language } from '@/lib/i18n'

type Props = {
  params: { lang: Language }
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { lang } = params
  const baseUrl = 'https://example.com'
  const pathname = '/about'

  const alternates = {
    canonical: `${baseUrl}/${lang}${pathname}`,
    languages: languages.reduce((acc, locale) => {
      acc[locale] = `${baseUrl}/${locale}${pathname}`
      return acc
    }, {} as Record<string, string>)
  }

  const titles: Record<Language, string> = {
    en: 'About Us - Learn More About Our Company',
    zh: '关于我们 - 了解更多关于我们公司的信息',
    ja: '私たちについて - 当社についてもっと知る',
  }

  const descriptions: Record<Language, string> = {
    en: 'Learn about our mission, values, and the team behind our success.',
    zh: '了解我们的使命、价值观以及我们成功背后的团队。',
    ja: '私たちの使命、価値観、そして成功を支えるチームについて学びます。',
  }

  return {
    title: titles[lang],
    description: descriptions[lang],
    alternates,
    openGraph: {
      title: titles[lang],
      description: descriptions[lang],
      url: `${baseUrl}/${lang}${pathname}`,
      siteName: 'Example Site',
      locale: lang,
      type: 'website',
    },
  }
}

export default function AboutPage({ params }: Props) {
  const content = {
    en: 'About us content in English...',
    zh: '关于我们的中文内容...',
    ja: '私たちについての日本語コンテンツ...',
  }

  return (
    <div>
      <h1>About Us</h1>
      <p>{content[params.lang]}</p>
    </div>
  )
}

6.5 Itinéraire dynamique avec hreflang

// app/[lang]/blog/[slug]/page.tsx
import { Metadata } from 'next'
import { languages, Language } from '@/lib/i18n'
import { getArticle, getAllArticles } from '@/lib/articles'
import { notFound } from 'next/navigation'

type Props = {
  params: { lang: Language; slug: string }
}

export async function generateStaticParams() {
  const articles = await getAllArticles()

  return languages.flatMap(lang =>
    articles.map(article => ({
      lang,
      slug: article.slug,
    }))
  )
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { lang, slug } = params
  const article = await getArticle(slug, lang)

  if (!article) {
    return {}
  }

  const baseUrl = 'https://example.com'
  const pathname = `/blog/${slug}`

  const alternates = {
    canonical: `${baseUrl}/${lang}${pathname}`,
    languages: languages.reduce((acc, locale) => {
      acc[locale] = `${baseUrl}/${locale}${pathname}`
      return acc
    }, {} as Record<string, string>)
  }

  return {
    title: article.title,
    description: article.excerpt,
    alternates,
    openGraph: {
      title: article.title,
      description: article.excerpt,
      url: `${baseUrl}/${lang}${pathname}`,
      type: 'article',
      publishedTime: article.publishedAt,
      authors: [article.author],
    },
  }
}

export default async function BlogArticle({ params }: Props) {
  const { lang, slug } = params
  const article = await getArticle(slug, lang)

  if (!article) {
    notFound()
  }

  return (
    <article>
      <h1>{article.title}</h1>
      <p>{article.content}</p>
    </article>
  )
}

6.6 Génération de plan de site

// app/sitemap.ts
import { MetadataRoute } from 'next'
import { languages } from '@/lib/i18n'
import { getAllArticles } from '@/lib/articles'

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const baseUrl = 'https://example.com'
  const sitemap: MetadataRoute.Sitemap = []

  const staticPages = ['', '/about', '/contact']
  staticPages.forEach(page => {
    languages.forEach(lang => {
      sitemap.push({
        url: `${baseUrl}/${lang}${page}`,
        lastModified: new Date(),
        changeFrequency: 'monthly',
        priority: page === '' ? 1 : 0.8,
        alternates: {
          languages: languages.reduce((acc, l) => {
            acc[l] = `${baseUrl}/${l}${page}`
            return acc
          }, {} as Record<string, string>)
        }
      })
    })
  })

  const articles = await getAllArticles()
  articles.forEach(article => {
    languages.forEach(lang => {
      sitemap.push({
        url: `${baseUrl}/${lang}/blog/${article.slug}`,
        lastModified: new Date(article.updatedAt),
        changeFrequency: 'weekly',
        priority: 0.6,
        alternates: {
          languages: languages.reduce((acc, l) => {
            acc[l] = `${baseUrl}/${l}/blog/${article.slug}`
            return acc
          }, {} as Record<string, string>)
        }
      })
    })
  })

  return sitemap
}

6.7 Robots.txt

// app/robots.ts
import { MetadataRoute } from 'next'

export default function robots(): MetadataRoute.Robots {
  return {
    rules: {
      userAgent: '*',
      allow: '/',
    },
    sitemap: 'https://example.com/sitemap.xml',
  }
}
```## 7. Validation et tests

### 7.1 Liste de contrôle locale

Avant le déploiement en production :

- [ ] `<html lang="...">` correct sur toutes les pages
- [ ] Balises hreflang complètes sur chaque page (toutes les versions linguistiques)
- [ ] Auto-référence présente en hreflang
- [ ] `x-default` pointe vers la langue par défaut
- [ ] points canoniques vers la version linguistique actuelle
- [ ] Le plan du site contient toutes les URL de langues
- [ ] robots.txt pointe vers sitemap.xml
- [ ] Qualité de traduction uniforme de toutes les versions linguistiques

### 7.2 Test des résultats enrichis de Google

[Test des résultats enrichis de Google](https://search.google.com/test/rich-results) :

1. Entrez l'URL de la page
2. Attendez l'exploration et l'analyse
3. Vérifiez les erreurs et les avertissements
4. Vérifiez la reconnaissance du hreflang

### 7.3 Outils d'inspection hreflang

- **Générateur hreflang Aleyda Solis** : https://www.aleydasolis.com/english/international-seo-tools/hreflang-tags-generator/
- **Vérificateur Merkle hreflang** : https://technicalseo.com/tools/hreflang/

Caractéristiques :
- Vérification groupée de plusieurs pages
- Identifier les problèmes de symétrie
- Rechercher les erreurs de code de langue

### 7.4 Console de recherche Google

Après déploiement :

1. Soumettez le plan du site à la Search Console
2. Attendez 1 à 2 semaines pour la première indexation
3. Cochez « Orientation internationale » > « Langue ».
4. Corrigez les erreurs et les avertissements hreflang
5. Surveiller les performances de recherche par langue## 8. Questions fréquemment posées

### Q1 : Différence entre hreflang et canonique ?

- **canonique** – URL canonique de la page, contre le contenu en double
- **hreflang** – versions linguistiques disponibles, pour le ciblage linguistique

Les deux peuvent être combinés. canonique par version linguistique sur elle-même, hreflang sur toutes les versions.

### Q2 : hreflang obligatoire sur chaque page ?

Oui. Sur **n'importe quelle** version linguistique, symétrique (référence mutuelle). La page en anglais ne suffit pas - Google l'ignore.

### Q3 : Où pointe x-default ?

Langue principalement standard ou version la plus générale :

- Public principal anglais → version anglaise
- Site mondial → Anglais international (en-US)
- Site régional → Langue principale de la région

### Q4 : Sous-répertoire ou sous-domaine ?

**Sous-répertoire (recommandé) :**
- SEO sur le domaine principal
- Mise en œuvre facile
- Pour la plupart des projets

**Sous-domaine :**
- Propre serveur par marché
- Grands sites internationaux avec leurs propres opérations de marché
- Coûts de domaine supplémentaires

**Conclusion :** Aucune exigence particulière → Sous-répertoire.

### Q5 : Contenu traduit automatiquement ?

Non recommandé pour le référencement :

- Google détecte les traductions faibles
- Valeur du contenu inférieure
- Mauvaise UX, taux de rebond élevé

Si vous avez un budget serré :

1. Les pages principales en premier (page d'accueil, principaux produits, trafic principal)
2. MT + édition humaine
3. Améliorer progressivement la qualité

### Q6 : Combien de temps avant l'indexation ?

Horaire type :

- Après la soumission du plan du site **1 à 2 semaines** première indexation
- Indexation complète **1-2 mois**
- Signaux SEO **3-6 mois**

Accélérer :
- Plan du site correct + soumission
- Haute qualité de contenu et mises à jour
- Des backlinks de qualité
- Demander l'indexation des pages importantes dans la Search Console## 9. Résumé

Le référencement multilingue est la clé du succès international. Points clés :### 9.1 Points clés

1. **Stratégie d'URL**
   - Sous-répertoire recommandé (example.com/en/, example.com/zh/)
   - Structure d'URL claire et cohérente

2. **hreflang**
   - Balises complètes sur chaque page
   - Auto-référence obligatoire
   - x-default à la langue par défaut
   - Codes de langue ISO 639-1

3. **Plan du site**
   - Toutes les versions linguistiques incluses
   - HReflang facultatif dans le plan du site
   - Mettre à jour et soumettre régulièrement

4. **Qualité du contenu**
   - Pas de version MT brute
   - Cohérence et professionnalisme
   - Localisation, pas seulement traduction

5. **Surveillance**
   - Google Search Console
   - Vérification régulière du hreflang
   - Suivre les performances par langue

### 9.2 Liste d'actions

- [ ] Sélectionner et mettre en œuvre la stratégie URL (sous-répertoire recommandé)
- [ ] Balises hreflang complètes sur toutes les pages
- [ ] Corriger les balises canoniques
- [ ] Générer un plan de site multilingue
- [ ] robots.txt avec référence au plan du site
- [ ] Soumettre le plan du site à Google Search Console et Bing
- [ ] Vérifiez le hreflang avec les outils de validation
- [ ] Garantir la qualité de la traduction
- [ ] Mettre en place un suivi et des revues régulières

### 9.3 Pour aller plus loin

- [Google : sites multilingues et multirégionaux](https://developers.google.com/search/docs/specialty/international)
- [Google : guide hreflang](https://developers.google.com/search/docs/specialty/international/localized-versions)
- [Next.js : internationalisation](https://nextjs.org/docs/app/building-your-application/routing/internationalization)
- [Schema.org : balisage multilingue](https://schema.org/docs/multilingual.html)

Un SEO multilingue bien configuré demande du temps, mais le retour est important : meilleur classement, utilisateurs mieux ciblés, conversion plus élevée. En suivant ces bonnes pratiques, votre site performera nettement mieux dans les moteurs de recherche.





Des questions sur la mise en œuvre ? Échangeons en commentaires !

Configuration SEO multilingue Next.js — flux complet

Étapes complètes : balises hreflang, sitemap multilingue et choix de la stratégie d'URL

⏱️ Estimated time: 2 hr

  1. 1

    Step 1: Configurer les balises hreflang

    Dans metadata :
    ```tsx
    // app/[locale]/about/page.tsx
    export async function generateMetadata({ params }): Promise<Metadata> {
    const { locale } = params

    return {
    title: 'About Us',
    alternates: {
    languages: {
    'zh': '/zh/about',
    'en': '/en/about',
    'x-default': '/en/about', // langue par défaut
    },
    },
    }
    }
    ```

    Points clés :
    • Inclure toutes les versions linguistiques
    • x-default vers la langue par défaut
    • Configurer sur chaque page

    Sortie HTML :
    ```html
    <link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />
    <link rel="alternate" hreflang="en" href="https://example.com/en/about" />
    <link rel="alternate" hreflang="x-default" href="https://example.com/en/about" />
    ```

    Effet :
    • Indiquer la langue cible aux moteurs de recherche
    • Éviter la pénalité duplicate content
    • Mieux matcher l'utilisateur
  2. 2

    Step 2: Générer un sitemap multilingue

    Méthode 1 : un sitemap par langue
    ```tsx
    // app/[locale]/sitemap.ts
    export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
    const baseUrl = 'https://example.com'
    const locale = params.locale

    return [
    {
    url: `${baseUrl}/${locale}`,
    lastModified: new Date(),
    changeFrequency: 'daily',
    priority: 1,
    },
    ]
    }
    ```

    Méthode 2 : index de sitemaps
    ```tsx
    // app/sitemap.ts
    export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
    const locales = ['zh', 'en']
    const baseUrl = 'https://example.com'

    return locales.flatMap(locale => [
    {
    url: `${baseUrl}/${locale}`,
    lastModified: new Date(),
    changeFrequency: 'daily',
    priority: 1,
    },
    ])
    }
    ```

    Points clés :
    • Inclure toutes les versions linguistiques
    • Format d'URL correct
    • Soumettre dans Google Search Console
  3. 3

    Step 3: Choisir la stratégie d'URL

    Option 1 : sous-répertoire (recommandé)
    • URL : /zh/about, /en/about
    • Configuration simple
    • SEO-friendly
    • Convient à la plupart des projets

    Option 2 : sous-domaine
    • URL : zh.example.com, en.example.com
    • Plusieurs domaines requis
    • Image plus professionnelle
    • Grands projets

    Option 3 : cookie
    • Langue via cookie
    • Pas de préfixe de langue dans l'URL
    • Peu favorable au SEO
    • Non recommandé

    Recommandation :
    • Plupart des projets → sous-répertoire
    • Grands projets → sous-domaine
    • À éviter → cookie

    Le sous-répertoire est le meilleur choix SEO.
  4. 4

    Step 4: Valider et tester

    Outils de validation :

    1. Google Search Console :
    • Soumettre le sitemap multilingue
    • Vérifier les balises hreflang
    • Consulter l'état d'indexation

    2. Outils de test hreflang :
    • https://www.aleydasolis.com/en/english-tools/international-seo-tools/hreflang-tags-validator/
    • Vérifier la configuration hreflang

    3. Sitemap multilingue :
    • Vérifier le format du sitemap
    • Toutes les versions linguistiques incluses
    • URLs correctes

    Erreurs typiques :
    • Balises hreflang manquantes
    • x-default incorrect
    • Sitemap incomplet
    • Formats d'URL incohérents

    Conseil : valider juste après la configuration.

FAQ

Qu'est-ce que la balise hreflang ? Pourquoi en a-t-on besoin ?
hreflang est un attribut HTML qui indique la langue et la région cibles d'une page.

Rôles principaux :
1. Éviter la pénalité duplicate content — les versions linguistiques sont des traductions, pas des doublons
2. Mieux matcher l'utilisateur — afficher la bonne version dans les résultats
3. Améliorer l'UX — éviter la mauvaise langue

Configuration :
```tsx
export async function generateMetadata({ params }): Promise<Metadata> {
return {
alternates: {
languages: {
'zh': '/zh/about',
'en': '/en/about',
'x-default': '/en/about',
},
},
}
}
```

Points clés :
• Toutes les versions linguistiques
• x-default vers la langue par défaut
• Sur chaque page

Selon Google, plus de 60 % des sites multilingues ont des erreurs hreflang.
Comment configurer les balises hreflang ?
Dans metadata :
```tsx
// app/[locale]/about/page.tsx
export async function generateMetadata({ params }): Promise<Metadata> {
const { locale } = params

return {
title: 'About Us',
alternates: {
languages: {
'zh': '/zh/about',
'en': '/en/about',
'x-default': '/en/about',
},
},
}
}
```

Sortie HTML :
```html
<link rel="alternate" hreflang="zh" href="https://example.com/zh/about" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/about" />
```

Points clés :
• Toutes les versions, y compris la page courante
• x-default vers la langue par défaut
• Utiliser des URLs absolues
Comment générer un sitemap multilingue ?
Méthode 1 : un sitemap par langue
```tsx
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const baseUrl = 'https://example.com'
const locale = params.locale

return [
{
url: `${baseUrl}/${locale}`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
]
}
```

Méthode 2 : index de sitemaps
```tsx
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const locales = ['zh', 'en']
const baseUrl = 'https://example.com'

return locales.flatMap(locale => [
{
url: `${baseUrl}/${locale}`,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
])
}
```

Points clés :
• Toutes les versions linguistiques
• Format d'URL correct
• Soumettre dans Google Search Console

Recommandation : index de sitemaps pour plus de flexibilité.
Quelle stratégie d'URL pour un site multilingue ?
Trois stratégies :

Option 1 : sous-répertoire (recommandé)
• /zh/about, /en/about
• Simple et SEO-friendly
• Plupart des projets

Option 2 : sous-domaine
• zh.example.com, en.example.com
• Plusieurs domaines, plus professionnel
• Grands projets

Option 3 : cookie
• Langue via cookie, pas de préfixe dans l'URL
• Peu favorable au SEO — à éviter

Recommandation : plupart des projets → sous-répertoire.

Important : les URLs hreflang doivent suivre la même stratégie.
Comment valider la configuration SEO multilingue ?
Outils :

1. Google Search Console :
• Soumettre le sitemap multilingue
• Vérifier hreflang
• Suivre l'indexation

2. Validateur hreflang :
• https://www.aleydasolis.com/en/english-tools/international-seo-tools/hreflang-tags-validator/

3. Validation du sitemap :
• Format, toutes les langues, URLs correctes

Erreurs typiques :
• hreflang manquant
• x-default incorrect
• Sitemap incomplet
• URLs incohérentes

Conseil : vérifier juste après la configuration et contrôler l'indexation régulièrement.
Quelles erreurs SEO multilingues sont fréquentes ?
Erreurs courantes :

1. Balises hreflang manquantes
• Le moteur ne connaît pas la langue
• Mauvaise version affichée

2. x-default incorrect
• Absent ou mauvaise langue

3. Sitemap incomplet
• Seulement une partie des langues soumise

4. Formats d'URL incohérents
• URLs hreflang qui ne correspondent pas à la stratégie

5. Duplicate content
• hreflang absent — versions traitées comme doublons

Solution :
• hreflang sur toutes les pages
• Référencer toutes les versions
• URLs correctes et sitemap complet

Suivre les bonnes pratiques de cet article.

18 min de lecture · Publié le: 25 déc. 2025 · Mis à jour le: 27 juil. 2026

Commentaires

Connectez-vous avec GitHub pour laisser un commentaire

Easton BlogEaston Blog