En bref
- Avec l’App Router, chaque page déclare son titre, sa description et sa canonical via l’API Metadata ; next/head appartient à l’ancien Pages Router et n’est pas le mécanisme à utiliser ici.
- Sur ce portfolio et sur Les Chemins de la Maison, le sitemap, le robots.txt et les données structurées se génèrent depuis les mêmes données que les pages, et la date lastModified ne change qu’après une vraie mise à jour.
- Une URL qui change passe par une redirection permanente. Dans next.config, permanent: true renvoie un code 308, que Google traite comme un 301.
- Une page « Détectée » attend d’être explorée, une page « Explorée » a été lue puis écartée. La première demande d’abord des liens internes, la seconde un contenu plus utile, une fois que Google a relu sa dernière version.
Les exemples de cet article viennent de deux sites que j’ai développés avec l’App Router de Next.js : ce portfolio, en Next.js 16, et Les Chemins de la Maison, en Next.js 15, le site d’une praticienne qui publie des articles sur des sujets de santé et d’enfance. S’y ajoute l’e-commerce Cora, en Next.js 14, un site bilingue dont nous avons mené la refonte en équipe. Next.js fournit l’essentiel des outils pour les métadonnées, le sitemap et les images de partage. La Search Console rappelle pourtant assez vite qu’une page bien balisée n’est pas indexée pour autant, et c’est souvent là que le travail continue.
Que reçoit Google d’un site Next.js ?
Google peut exécuter le JavaScript, mais cela ne dispense pas de fournir un HTML exploitable dès le départ. Avec l’App Router, une page peut être rendue côté serveur ou générée au build, de sorte que son contenu, ses métadonnées et ses données structurées figurent dans le HTML initial.
Les trois projets n’ont pas fait le même choix. Toutes les pages de ce portfolio sont pré-rendues au build, celles des articles et des projets grâce à generateStaticParams, car son contenu ne change qu’au déploiement. Sur Les Chemins de la Maison, les articles sont pré-rendus au build grâce à generateStaticParams, tandis que la liste du blog est rendue à chaque requête, puisqu’elle dépend des filtres et de la pagination passés dans l’URL. Cora illustre un troisième cas. Presque tout y dépend du magasin choisi, mémorisé dans un cookie, et nous avons donc retenu le rendu côté serveur à chaque requête, comme le raconte l’article sur la refonte de Cora. Le mode de rendu se choisit page par page, et le guide SSR, ISR ou statique détaille ces arbitrages.
Des métadonnées propres à chaque page
Dans l’App Router, les métadonnées passent par l’objet metadata ou la fonction generateMetadata, exportés depuis un layout ou une page. Sur ce portfolio, le layout racine fixe les valeurs communes, dont metadataBase pour que les URL relatives soient résolues en URL absolues, et un modèle de titre qui ajoute le nom du site à chaque page :
// app/layout.tsx (simplified)
export const metadata: Metadata = {
metadataBase: new URL(SEO_CONFIG.baseUrl),
title: {default: HOME_TITLE, template: `%s — ${SEO_CONFIG.name}`},
description: HOME_DESCRIPTION,
};
Une page à segment dynamique, comme /blog/[slug], calcule ensuite ses propres métadonnées à partir de ses données. Depuis Next.js 15, params est une Promise qu’il faut attendre :
// app/blog/[slug]/page.tsx (simplified)
export async function generateMetadata({params}: Props): Promise<Metadata> {
const {slug} = await params;
const post = getBlogPost(slug);
if (!post) return {};
const url = `${SEO_CONFIG.baseUrl}/blog/${slug}`;
return {
title: pageTitle(post.title),
description: post.metaDescription,
alternates: {canonical: url},
};
Sur Les Chemins de la Maison, un helper unique construit l’objet metadata de chaque page à partir de son titre, de sa description et de son chemin. La canonical, l’URL Open Graph et l’image de partage en dérivent, si bien qu’une page qui passe par lui ne peut pas les oublier. Ce type de base partagée compte d’autant plus que Next.js fusionne les métadonnées de façon superficielle. Un objet imbriqué comme openGraph ou alternates, redéclaré plus bas dans l’arbre des routes, remplace celui du parent au lieu de le compléter.
Cora est bilingue (FR / NL) : la fonction generateMetadata de son layout racine choisit le titre et la description selon la langue du visiteur (« L’hypermarché » ou « Hypermarkt »), et l’attribut lang de la balise html suit la même valeur.
La canonical indique à Google l’URL que je considère comme principale lorsqu’une même ressource est accessible par plusieurs adresses. Google la traite comme une indication forte, qu’il reste libre de ne pas suivre. Pour les titres et les descriptions, il n’existe pas de longueur maximale garantissant un affichage complet dans Google, qui dépend de la largeur disponible et de la requête. Sur ce portfolio, le helper pageTitle retire le suffixe « — Jordan Philippot » quand le titre complet dépasserait 60 caractères, et chaque page garde des textes qui lui sont propres, car des métadonnées trop proches d’une page à l’autre rendent le contenu moins distinctif et peuvent conduire Google à générer ses propres titres ou extraits. La balise meta keywords, elle, n’a aucun effet sur Google et n’a donc pas sa place dans ce portfolio.
Toutes ces URL reposent sur l’adresse de base du site. Mal renseignée en production, elle fausserait d’un coup les canonicals, le sitemap, le JSON-LD et les images de partage. Sur ce portfolio et sur Les Chemins de la Maison, elle est donc déclarée à un seul endroit, et le portfolio la lit avec une valeur de repli explicite.
Sitemap et robots générés depuis les données
Les fichiers sitemap.ts et robots.ts de l’App Router produisent sitemap.xml et robots.txt depuis le code. Le sitemap est construit à partir des mêmes données que les pages, un nouvel article y apparaît donc automatiquement, et sa valeur lastModified reprend la date de mise à jour de l’article, ou à défaut sa date de publication :
// app/sitemap.ts (simplified)
export default function sitemap(): MetadataRoute.Sitemap {
const blogRoutes = blogPosts.map((post) => ({
url: `${BASE_URL}/blog/${post.slug}`,
lastModified: getLastModified(post), // update date, else publication date
}));
return [...staticRoutes, ...blogRoutes, ...projectRoutes];
}
Les Chemins de la Maison suit le même principe avec ses articles. Une date lastModified ne sert que si elle correspond à une modification réelle de la page. Une valeur recalculée à chaque build annonce un changement qui n’a pas eu lieu et n’aide pas Google à repérer les pages qui ont vraiment évolué. Sur ces deux sites, le robots.txt autorise les pages publiques et donne l’adresse du sitemap.
Le sitemap de Cora est construit autrement. Il parcourt l’arborescence de src/app et y ajoute les pages créées dans le CMS Prismic, en écartant les pages privées comme le compte, le panier ou les listes. Une page statique ajoutée dans le code, ou une page créée dans le CMS, y apparaît donc sans intervention.
Quelles données structurées pour quel site ?
Les données structurées décrivent le contenu d’une page dans le vocabulaire de Schema.org, généralement au format JSON-LD. Elles aident les moteurs à comprendre une personne, un article ou un fil d’Ariane, et certains types peuvent donner lieu à des résultats enrichis lorsqu’ils sont pris en charge et correctement implémentés. Ce portfolio présente une personne et son travail. Chaque type sort d’un builder unique dans lib/seo.ts :
- WebSite sur toutes les pages, pour identifier le site ;
- Person et ProfilePage sur l’accueil, avec le nom, le métier, la localité, les profils LinkedIn et GitHub, et la photo de profil déclarée comme image principale de la page ;
- BlogPosting sur chaque article, avec ses dates, son auteur, son image et les sources affichées en fin d’article, reprises en citation ;
- CreativeWork sur chaque projet ;
- BreadcrumbList sur les articles et les projets, pour le fil d’Ariane.
Les Chemins de la Maison décrit plutôt une activité. Chaque page de service porte un objet Service, chaque article un Article, et la page des tarifs une liste des services avec leur Offer. Ces tarifs viennent du même fichier que les prix affichés sur les pages, ce qui évite qu’un montant diffère entre le texte et le JSON-LD ; seuls les montants uniques y figurent, les fourchettes de prix restant hors du balisage.
Les pages de service portent aussi un FAQPage pour leurs questions fréquentes. Depuis 2023, Google réservait les résultats enrichis FAQ aux sites gouvernementaux ou de santé reconnus, et il les a retirés de la recherche le 7 mai 2026. Le balisage reste valide au sens de Schema.org, mais il ne faut plus en attendre d’affichage particulier dans Google.
Sur le portfolio comme sur Les Chemins de la Maison, le JSON-LD est inséré par une balise script dont le contenu échappe le caractère <, pour qu’un texte contenant une balise ne puisse pas refermer le script prématurément :
// components/JsonLd.tsx (simplified)
export default function JsonLd({data}: JsonLdProps) {
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{__html: JSON.stringify(data).replace(/</g, '\\u003c')}}
/>
);
}
Le Rich Results Test de Google vérifie les types qu’il prend en charge et signale les erreurs ou les propriétés manquantes. Pour un article, Google recommande des dates au format ISO 8601, avec le fuseau horaire quand l’heure de publication compte.
Comment changer d’URL sans perdre son référencement ?
Une URL publiée finit par être connue de Google, partagée et parfois liée depuis d’autres sites. Quand elle doit changer, Google recommande une redirection permanente côté serveur, qu’il interprète comme un signal pour faire de la nouvelle adresse l’URL canonique. Dans Next.js, la clé redirects de next.config s’en charge. Avec permanent: true, elle renvoie un code 308, l’équivalent d’un 301 qui conserve la méthode de la requête, et Google traite les deux de la même façon.
// next.config.mjs (Les Chemins de la Maison, simplified)
async redirects() {
return [
// Slug shortened on 2026-10-09 (year removed).
{
source: '/blog/financer-formation-opco-uniformation-educateurs-2026',
destination: '/blog/financer-formation-opco-uniformation-educateurs',
permanent: true,
},
];
}
J’ai retiré l’année de ce slug le 9 octobre, et l’ancienne adresse redirige depuis vers la nouvelle. Dans la Search Console, elle apparaît encore en « soft 404 », le statut que Google donne à une page qui répond normalement mais qu’il juge vide ou introuvable. Ce constat date de son dernier passage, fin juillet, donc d’avant la redirection, et il restera affiché tant que Google n’aura pas réexploré l’URL. La validation du motif, dans le rapport d’indexation, sert justement à le lui demander.
Le soft 404 se prévient aussi dans le code. Dans Cora, une page éditoriale est cherchée dans le dépôt Prismic principal, puis dans celui du magasin ; si elle n’existe dans aucun des deux, notFound() renvoie une vraie 404, au lieu d’une page vide que Google pourrait classer en soft 404. Ce portfolio obtient le même résultat avec dynamicParams = false sur les routes des articles et des projets : un slug absent des données renvoie une 404.
Sur la refonte de Cora, le même mécanisme a servi à plus grande échelle, puisque nous avons déclaré 42 redirections permanentes des anciennes URL vers les nouvelles dans next.config. Une redirection permanente ne se pose toutefois que pour un changement définitif, car la documentation de Next.js rappelle que navigateurs et moteurs la mémorisent sans limite de durée.
Image de partage, RSS et llms.txt
Quand une page est partagée sur LinkedIn ou dans une messagerie, l’aperçu utilise son image Open Graph. Ce portfolio générait d’abord des cartes au build avec un fichier opengraph-image.tsx et ImageResponse : le titre de chaque article, dans les polices et les couleurs du site. Je les ai remplacées par ma photo, déclarée dans openGraph.images sur toutes les pages sauf les fiches projet, qui gardent la capture du projet. Google peut reprendre cette image comme vignette dans ses résultats et déconseille celles qui contiennent du texte ; sur l’accueil, og:image et primaryImageOfPage, les deux indications qu’il accepte pour l’image préférée, désignent la même photo. Les Chemins de la Maison suit la même logique, avec une illustration déclarée par chaque article et le portrait de la praticienne par défaut sur les autres pages.
Si une route combine les deux mécanismes, le fichier opengraph-image est ignoré dès que l’objet openGraph contient une clé images, contrairement à ce que laisse entendre la documentation, qui donne la priorité aux fichiers. Je l’avais vérifié dans le code source de Next.js 16 (resolve-metadata.js) quand ce portfolio utilisait encore les cartes générées.
Le portfolio publie aussi un flux RSS, construit au build par un route handler statique (dynamic = "force-static") et déclaré dans les métadonnées par alternates.types, ainsi qu’un llms.txt généré depuis les données des projets et des articles. Ce dernier suit une convention proposée pour donner aux assistants IA une version synthétique d’un site ; ce n’est pas un standard de Google, et rien ne garantit qu’un assistant le lise ou cite le site.
Pourquoi Google n’indexe-t-il pas une page ?
Le rapport d’indexation de la Search Console classe les pages non indexées par motif, et deux d’entre eux reviennent sur ces sites. « Détectée, actuellement non indexée » signifie que Google connaît l’URL mais a reporté son exploration. « Explorée, actuellement non indexée » veut dire qu’il l’a lue sans la retenir pour le moment, et Google précise qu’il est inutile de redemander l’indexation d’une telle page sans l’avoir modifiée.
Sur Les Chemins de la Maison, le nombre de pages indexées est passé de 21 à 40 le 10 octobre, au lendemain d’une révision du contenu des articles. Je ne peux pas attribuer toute la hausse à cette seule révision, mais le détail du rapport aide à choisir la suite. Les 8 articles encore « Explorée » avaient été lus en juin et en juillet, donc jugés sur leur ancienne version : une demande d’indexation passe avant toute réécriture. Parmi les 14 pages « Détectée », jamais explorées, figurent la page des services et l’une des pages de service, pourtant présentes dans la navigation. Cela laisse penser que Google explore encore peu le site, et le travail porte d’abord sur le maillage interne.
Ce portfolio montre un autre cas. Dans l’export Search Console du 15 octobre, 20 des 32 URL du sitemap étaient indexées, et six articles n’avaient pas encore été vus par Google ; ils avaient été mis en ligne à la fin de son dernier grand passage, du 1er au 3 octobre, ou après. L’article sur l’accessibilité, lui, avait été lu le 13 octobre puis écarté. Je l’ai repris en profondeur à partir d’audits réels, et sa nouvelle version attend maintenant le prochain passage de Google.
Que vérifier après la mise en ligne ?
- la Search Console : soumettre le sitemap, inspecter les pages nouvelles ou réécrites, puis suivre les motifs du rapport d’indexation ;
- les anciennes URL, qui doivent répondre par une redirection permanente vers la nouvelle adresse ;
- le Rich Results Test sur une page de chaque type comportant des données structurées ;
- le LinkedIn Post Inspector sur un article, pour vérifier l’image et les métadonnées de partage, et le W3C Feed Validator pour le flux RSS.
Viennent ensuite les mesures dans la durée, avec les pages indexées, les requêtes qui amènent des visites et les Core Web Vitals. Elles permettent de confronter les choix techniques aux résultats réels et de décider où concentrer les ajustements suivants.
Sur ce portfolio et sur Les Chemins de la Maison, le SEO technique repose sur une seule source pour chaque information qui compte (URL de base, dates, et tarifs pour le second) et sur des fichiers générés depuis les mêmes données que les pages. Sur les trois projets, les URL qui changent passent par une redirection permanente. La Search Console compte ensuite autant que le code, car c’est elle qui dit si Google a lu la dernière version d’une page avant de la juger. Il me reste à suivre, dans les semaines qui viennent, les pages que Google n’a pas encore relues sur ce portfolio et sur Les Chemins de la Maison.
Pour en discuter, écrivez-moi.
Sources
- Next.js — generateMetadata et l’objet metadata
- Next.js — redirects (next.config.js)
- Google Search Central — Mises à jour de la documentation (fin des résultats enrichis FAQ, mai 2026)
- Aide Search Console — Rapport sur l’indexation des pages
- Google Search Central — Bonnes pratiques pour le référencement des images
Publié à l’origine sur jordan-philippot.fr.
Top comments (0)