L'App Router a remplacé next/head par la Metadata API typée. Deux usages : l'export statique pour les pages fixes, generateMetadata pour les pages dynamiques :
// app/layout.tsx — réglages par défaut du site avec un modèle de titre
import type { Metadata } from "next";
export const metadata: Metadata = {
metadataBase: new URL("https://example.com"),
title: {
template: "%s | Acme",
default: "Acme — studio de développement web",
},
description: "Description par défaut utilisée quand une page n'en définit pas.",
};
// app/blog/[slug]/page.tsx — métadonnées dynamiques par article
export async function generateMetadata({ params }): Promise<Metadata> {
const post = await getPost(params.slug);
if (!post) return { title: "Article introuvable" };
return {
title: post.title, // via le modèle, deviendra « Titre de l'article | Acme »
description: post.excerpt,
};
}
Des règles pratiques qui influent réellement sur les positions :
- Title : ~50–60 caractères, mot-clé principal près du début, unique pour chaque page. Google réécrit les titres faibles : ne lui en donnez pas de raison.
- Description : ~150–160 caractères. N'influe pas directement sur le classement, mais c'est votre texte publicitaire dans les résultats et elle fait monter le CTR.
metadataBaseest obligatoire : sans lui, les adresses relatives des images OG et des canonicals se cassent en production.- Dédupliquez la requête entre
generateMetadataet le composant de page via Reactcache(), sinon vous interrogez le CMS deux fois à chaque requête.
import { cache } from "react";
export const getPost = cache(async (slug: string) => {
return fetchPostFromCMS(slug);
});
Checklist :
-
metadataBasedéfini dans le layout racine - Modèle de titre défini une seule fois, les pages ne renseignent que leur partie
- Chaque page a un title et une description uniques
- Les pages issues du CMS utilisent
generateMetadataavec un fetch enveloppé danscache() - Métadonnées de repli pour la 404 / le contenu manquant
