Liaison des versions linguistiques alternatives (hreflang)

Informer les moteurs de recherche de vos pages localisées

Problème

Une application dispose d'un contenu identique disponible à /en/page et /fr/page. Les moteurs de recherche les considèrent comme deux pages distinctes et concurrentes. Sans mécanisme pour les relier, les classements de recherche sont divisés, et les utilisateurs en France peuvent voir la page anglaise dans les résultats de recherche au lieu de la page française.

Solution

Utilisez la propriété alternates dans la fonction generateMetadata de Next.js. En fournissant une liste de toutes les langues disponibles pour une page donnée, Next.js générera automatiquement des balises <link rel="alternate" hreflang="..." /> dans le <head> du document, signalant la relation entre ces pages aux moteurs de recherche.

Étapes

1. Définir l'URL de base de votre site

Les balises hreflang nécessitent des URL absolues, et non relatives. Stockez l'URL de base canonique de votre site dans un fichier de configuration.

// i18n-config.ts

export const locales = ['en', 'es', 'fr'];
export const defaultLocale = 'en';
export const siteBaseUrl = 'https://www.example.com'; // Votre URL de production

2. Ajouter alternates à generateMetadata

Dans votre fichier app/[lang]/layout.tsx (pour l'appliquer à toutes les pages) ou dans un fichier de page spécifique, exportez une fonction generateMetadata.

// app/[lang]/layout.tsx
import { locales, siteBaseUrl } from '@/i18n-config';
import type { Metadata } from 'next';

type Props = {
  params: { lang: string };
  children: React.ReactNode;
};

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

  // Créer les alternatives linguistiques
  const alternatesMap = locales.reduce((acc, locale) => {
    acc[locale] = `${siteBaseUrl}/${locale}`;
    return acc;
  }, {} as Record<string, string>);

  return {
    alternates: {
      canonical: `${siteBaseUrl}/${lang}`,
      languages: {
        ...alternatesMap,
        'x-default': `${siteBaseUrl}/${defaultLocale}`,
      },
    },
  };
}

// Reste de votre composant de mise en page
export default function RootLayout({ children, params }: Props) {
  return (
    <html lang={params.lang}>
      <body>{children}</body>
    </html>
  );
}

Ce code génère un objet languages qui associe chaque locale à son URL de base absolue (par exemple, en: 'https://www.example.com/en'). Il définit également une URL canonical pour la page actuelle et une URL x-default, qui indique aux moteurs de recherche quelle version afficher pour les utilisateurs dont la langue n'est pas spécifiée.

3. Gérer les alternates sur les pages imbriquées

Pour les pages imbriquées comme /about, vous devez vous assurer que la fonction metadata inclut le chemin complet.

// app/[lang]/about/page.tsx
import { locales, siteBaseUrl, defaultLocale } from '@/i18n-config';
import type { Metadata } from 'next';

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

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { lang } = params;
  const path = '/about'; // Le chemin pour cette page

  // Créer les alternatives linguistiques
  const alternatesMap = locales.reduce((acc, locale) => {
    acc[locale] = `${siteBaseUrl}/${locale}${path}`;
    return acc;
  }, {} as Record<string, string>);

  return {
    title: 'About Us', // Ajoutez votre titre traduit
    alternates: {
      canonical: `${siteBaseUrl}/${lang}${path}`,
      languages: {
        ...alternatesMap,
        'x-default': `${siteBaseUrl}/${defaultLocale}${path}`,
      },
    },
  };
}

export default function AboutPage() {
  return <div>About page content</div>;
}