Guide SEO Next.js complet : Metadata API + données structurées en pratique

Le tableau de bord Google Search Console affiche un gros « 0 » — jour 23 après le lancement. Deux mois de dev, des nuits blanches, une UI soignée, une UX fluide : tout réduit à néant face à ce chiffre glacial.
Pire encore : en partageant le lien sur Twitter, l’aperçu reste vide. Pas même une couverture décente.
« Next.js a le SSR intégré, pourquoi le SEO est-il si mauvais ? » Après avoir parcouru la doc officielle, une vérité cruelle : le SSR ne signifie pas SEO-friendly. Meta tags mal configurés, données structurées absentes, Open Graph ignoré — les moteurs vous traitent comme de l’air.
Ce douleur, vous la connaissez peut-être : un produit abouti, introuvable en recherche, peu professionnel au partage, promotion payante obligatoire. Avec la Metadata API de Next.js 15 et quelques réglages clés, ces problèmes se résolvent.
Ce guide vous montre : metadata uniques par page via la Metadata API, données structurées pour des SERP plus attractives, aperçus sociaux impeccables. Et surtout 5 pièges SEO que j’ai moi-même frôlés.
Pourquoi le SEO de votre site Next.js est mauvais ?
Le SSR ne signifie pas SEO-friendly
Au début, je pensais pareil : Next.js, SSR, HTML servi aux crawlers — SEO parfait, non ?
Naïf.
En regardant le code d’un ami dans les DevTools, toutes les pages avaient <title>My App</title>, <meta name="description"> identique ou absent. Comme une boutique haut de gamme avec une enseigne « Magasin » — personne ne sait ce que vous vendez.
Next.js fournit le SSR, mais vous devez configurer les meta tags. Sans ça, du HTML vide que Google ne comprend pas.
5 erreurs SEO fatales
Trop de devs — moi inclus — tombent dans ces pièges :
1. Un seul title et description pour toutes les pages
L’erreur la plus courante : title figé dans _document.tsx ou absent. Google affiche le même titre pour accueil, à propos et produits.
Comme des livres avec le même titre en couverture — vous achèteriez ?
2. Oublier l’URL canonique → contenu dupliqué
Piège discret : pagination (?page=2), filtres (?category=tech), tri (?sort=date) — mêmes vues, URLs différentes. Sans canonical, Google pénalise le « duplicate content ».
Un blog client a perdu 40 % de trafic pour ça ; canonical ajouté, retour à la normale en deux semaines.
3. Pas de données structurées → rich snippets manqués
En cherchant « recette tarte aux pommes », certaines fiches affichent note, temps, calories — ce n’est pas deviné, c’est du JSON-LD.
Les sites avec données structurées voient en moyenne +20 à 30 % de CTR — l’équivalent de +30 % de trafic pour quelques lignes de code.
4. Images sans alt → trafic image search perdu
Beaucoup croient que alt sert seulement l’accessibilité. Faux.
Google Images est un canal énorme. Un site de ressources design tire 30 % de son trafic de là grâce à des alt soignés.
5. Pas de sitemap.xml ni robots.txt
Ils indiquent quelles pages crawler et lesquelles éviter. Sans sitemap, Google met des mois à découvrir un nouvel article ; avec soumission Search Console, indexation en jours.
Next.js App Router génère sitemap.ts et robots.ts — pourquoi s’en priver ?
Maîtriser la Metadata API (Next.js 15)
La Metadata API est le plus grand cadeau SEO de Next.js. Fini le <Head> page par page : exportez un objet ou une fonction.
Metadata statique : pages à contenu fixe
Pour « À propos », politique de confidentialité, etc. :
// app/about/page.tsx
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'À propos - TechBlog',
description: 'Une équipe de développeurs passionnés partageant frontend, backend et DevOps.',
keywords: ['blog tech', 'développement frontend', 'Next.js', 'React'],
authors: [{ name: 'Zhang San' }],
openGraph: {
title: 'À propos - TechBlog',
description: 'Blog tech, retours d\'expérience dev',
url: 'https://yourdomain.com/about',
siteName: 'TechBlog',
images: [
{
url: 'https://yourdomain.com/og-about.jpg',
width: 1200,
height: 630,
}
],
type: 'website',
},
twitter: {
card: 'summary_large_image',
title: 'À propos - TechBlog',
description: 'Blog tech, retours d\'expérience dev',
images: ['https://yourdomain.com/og-about.jpg'],
},
}
export default function AboutPage() {
return <div>Contenu à propos...</div>
}
Typage, autocomplétion IDE, déduplication automatique des meta tags.
Bonnes pratiques :
title: 60 caractères max (troncature en SERP)description: 150-160 caractèresopenGraph.images: 1200x630 px (Twitter, Facebook, LinkedIn)
Metadata dynamique : blogs et fiches produit
generateMetadata brille pour les articles :
// app/blog/[slug]/page.tsx
import { Metadata } from 'next'
import { getPostBySlug } from '@/lib/posts'
export async function generateMetadata(
{ params }: { params: { slug: string } }
): Promise<Metadata> {
const post = await getPostBySlug(params.slug)
return {
title: `${post.title} - TechBlog`,
description: post.excerpt,
authors: [{ name: post.author }],
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.coverImage],
type: 'article',
publishedTime: post.publishedAt,
authors: [post.author],
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.excerpt,
images: [post.coverImage],
},
}
}
export default async function BlogPostPage({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug)
return <article>{post.content}</article>
}
Chaque article a son SEO ; Google reçoit du HTML complet sans attendre le JS client.
Astuce : metadataBase
Pour des chemins relatifs (/images/cover.jpg), configurez dans le root layout :
// app/layout.tsx
export const metadata: Metadata = {
metadataBase: new URL('https://yourdomain.com'),
}
Les URLs relatives deviennent absolues — sinon Open Graph échoue et le partage n’a pas d’image.
Templates : format de titre uniforme
« Page | TechBlog » partout ? Utilisez title.template :
// app/layout.tsx (root layout)
export const metadata: Metadata = {
title: {
template: '%s | TechBlog',
default: 'TechBlog - Blog technique',
},
description: 'Blog tech : frontend, backend, DevOps',
metadataBase: new URL('https://yourdomain.com'),
}
Page enfant :
// app/about/page.tsx
export const metadata: Metadata = {
title: 'À propos', // rendu : "À propos | TechBlog"
}
Accueil sans suffixe : title.absolute :
// app/page.tsx
export const metadata: Metadata = {
title: {
absolute: 'TechBlog - Accueil du blog technique',
},
}
Cohérence des titres ; un changement dans le root layout suffit.
Données structurées (Schema.org) pour se démarquer
Qu’est-ce que c’est et pourquoi c’est important ?
Cherchez « recette tarte aux pommes » : certaines fiches montrent note (4,8), temps (45 min), calories — d’autres seulement titre et extrait.
La différence : JSON-LD.
Format standard pour dire aux moteurs : « article de blog, auteur X, publié le Y » ou « produit, prix Z, note W ». Résultat : rich snippets (notes, prix, auteur).
+20 à 30 % de CTR en moyenne — coût quasi nul.
Types Schema courants
Parmi des centaines, les plus utiles :
- Organization — entreprise (page d’accueil)
- BlogPosting — articles de blog
- Product — e-commerce (prix, note, stock)
- FAQPage — FAQ dépliable en SERP
- LocalBusiness — commerce local (adresse, horaires)
Focus ici sur les deux premiers.
JSON-LD dans Next.js
Le composant <Script> simplifie tout :
// components/StructuredData.tsx
import Script from 'next/script'
type StructuredDataProps = {
data: object
}
export default function StructuredData({ data }: StructuredDataProps) {
return (
<Script
id="structured-data"
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }}
/>
)
}
Exemple 1 : Organization
// app/layout.tsx (root layout)
import StructuredData from '@/components/StructuredData'
const organizationData = {
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'TechBlog',
url: 'https://yourdomain.com',
logo: 'https://yourdomain.com/logo.png',
sameAs: [
'https://twitter.com/yourusername',
'https://github.com/yourcompany',
'https://linkedin.com/company/yourcompany',
],
contactPoint: {
'@type': 'ContactPoint',
email: '[email protected]',
contactType: 'Customer Service',
},
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
{children}
<StructuredData data={organizationData} />
</body>
</html>
)
}
Exemple 2 : BlogPosting
// app/blog/[slug]/page.tsx
import StructuredData from '@/components/StructuredData'
import { getPostBySlug } from '@/lib/posts'
export default async function BlogPostPage({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug)
const articleData = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
description: post.excerpt,
image: post.coverImage,
author: {
'@type': 'Person',
name: post.author,
url: `https://yourdomain.com/author/${post.authorSlug}`,
},
publisher: {
'@type': 'Organization',
name: 'TechBlog',
logo: {
'@type': 'ImageObject',
url: 'https://yourdomain.com/logo.png',
},
},
datePublished: post.publishedAt,
dateModified: post.updatedAt,
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `https://yourdomain.com/blog/${post.slug}`,
},
}
return (
<>
<article>{post.content}</article>
<StructuredData data={articleData} />
</>
)
}
Beaucoup de code, mais ce sont les métadonnées de l’article en format standard. Configurez une fois, copiez ensuite.
Valider les données structurées
-
Google Rich Results Test (https://search.google.com/test/rich-results)
- URL → quels rich results sont possibles
- Erreurs explicites
-
Schema Markup Validator (https://validator.schema.org/)
- Conformité Schema.org, plus strict que Google
Erreurs fréquentes : publisher manquant (BlogPosting), URL d’image non absolue. Corrigez et retestez.
Open Graph et Twitter Cards en pratique
Pourquoi le partage social compte
Partage Twitter ou Facebook : aperçu vide ou mauvaise image — peu professionnel. Bien configuré : belle couverture, titre, extrait — 2 à 3× plus de clics.
Open Graph et Twitter Cards contrôlent l’affichage des liens.
Protocole Open Graph
Standard Facebook, adopté par Twitter, LinkedIn, Slack, Discord.
Champs clés :
export const metadata: Metadata = {
openGraph: {
title: 'Titre de l\'article',
description: 'Résumé, ~150 caractères',
url: 'https://yourdomain.com/article',
siteName: 'TechBlog',
images: [
{
url: 'https://yourdomain.com/og-image.jpg',
width: 1200,
height: 630,
alt: 'Description image (accessibilité et SEO)',
},
],
type: 'article',
publishedTime: '2025-01-15T08:00:00.000Z',
modifiedTime: '2025-01-16T10:30:00.000Z',
authors: ['Zhang San', 'Li Si'],
tags: ['Next.js', 'SEO', 'développement frontend'],
locale: 'zh_CN',
alternateLocale: ['en_US', 'ja_JP'],
},
}
Dimensions image : 1200x630 (ratio 1,91:1), parfait sur Facebook, LinkedIn, Twitter, Slack. Max 8 Mo.
Twitter Cards
Twitter a ses propres meta tags ; configurez-les explicitement :
export const metadata: Metadata = {
twitter: {
card: 'summary_large_image',
site: '@yourusername',
creator: '@authorusername',
title: 'Titre de l\'article',
description: 'Résumé',
images: ['https://yourdomain.com/twitter-image.jpg'],
},
}
summary: petite vignettesummary_large_image: grande image (recommandé)
Limite Twitter : 5 Mo par image.
Génération dynamique d’images OG (avancé)
Créer manuellement 1200x630 par article ? Épuisant.
Next.js 13.3+ génère les OG par code :
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og'
import { getPostBySlug } from '@/lib/posts'
export const runtime = 'edge'
export const alt = 'Couverture article de blog'
export const size = {
width: 1200,
height: 630,
}
export const contentType = 'image/png'
export default async function Image({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug)
return new ImageResponse(
(
<div
style={{
fontSize: 60,
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
width: '100%',
height: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
color: 'white',
padding: '80px',
}}
>
<h1 style={{ fontSize: 72, fontWeight: 'bold', textAlign: 'center' }}>
{post.title}
</h1>
<p style={{ fontSize: 36, marginTop: 20, opacity: 0.9 }}>
by {post.author}
</p>
</div>
),
{
...size,
}
)
}
Image de partage générée depuis le titre — sans design manuel. Voir la doc next/og pour polices et arrière-plans custom.
Tester le partage social
-
Facebook Sharing Debugger (https://developers.facebook.com/tools/debug/)
- Aperçu Facebook ; « Scrape Again » pour vider le cache OG
-
Twitter Card Validator (https://cards-dev.twitter.com/validator)
- Compte développeur requis depuis 2023 ; sinon test en publiant un tweet
-
LinkedIn Post Inspector (https://www.linkedin.com/post-inspector/)
Piège vécu : nouvelle image OG, Facebook montre l’ancienne — rafraîchissez le cache dans Sharing Debugger.
Autres configurations SEO indispensables
Metadata API, données structurées et Open Graph sont le cœur ; ne négligez pas le reste.
sitemap.xml — lister vos pages
Fichier XML listant les URL. Google et Bing accélèrent l’indexation.
Créez app/sitemap.ts :
// app/sitemap.ts
import { MetadataRoute } from 'next'
import { getAllPosts } from '@/lib/posts'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getAllPosts()
const baseUrl = 'https://yourdomain.com'
const staticPages: MetadataRoute.Sitemap = [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: 'daily',
priority: 1,
},
{
url: `${baseUrl}/about`,
lastModified: new Date(),
changeFrequency: 'monthly',
priority: 0.8,
},
]
const blogPages: MetadataRoute.Sitemap = posts.map((post) => ({
url: `${baseUrl}/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
changeFrequency: 'weekly' as const,
priority: 0.7,
}))
return [...staticPages, ...blogPages]
}
Disponible sur https://yourdomain.com/sitemap.xml.
Ensuite :
- Soumettre à Google Search Console
- Soumettre à Bing Webmaster Tools
Indexation +50 % plus rapide en pratique : sans sitemap, deux semaines ; avec, trois jours.
robots.txt — permissions des crawlers
Indique ce qui est crawlable :
// app/robots.ts
import { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: [
{
userAgent: '*',
allow: '/',
disallow: ['/admin', '/api', '/private'],
},
],
sitemap: 'https://yourdomain.com/sitemap.xml',
}
}
Génère https://yourdomain.com/robots.txt :
User-agent: *
Allow: /
Disallow: /admin
Disallow: /api
Disallow: /private
Sitemap: https://yourdomain.com/sitemap.xml
Cas typiques : /admin et /api en disallow ; brouillons via noindex ou Disallow.
URL canonique — éviter la pénalité duplicate
Déclare l’URL « officielle » parmi plusieurs variantes (pagination, filtres, tri).
// app/blog/page.tsx
export const metadata: Metadata = {
alternates: {
canonical: 'https://yourdomain.com/blog',
},
}
Dynamique :
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
return {
alternates: {
canonical: `https://yourdomain.com/blog/${params.slug}`,
},
}
}
Multilingue avec alternates.languages :
export const metadata: Metadata = {
alternates: {
canonical: 'https://yourdomain.com/blog/nextjs-seo',
languages: {
'en-US': 'https://yourdomain.com/en/blog/nextjs-seo',
'ja-JP': 'https://yourdomain.com/ja/blog/nextjs-seo',
},
},
}
Optimisation images — next/image + alt
Google Images = gros trafic potentiel.
1. next/image plutôt que <img>
import Image from 'next/image'
<Image
src="/cover.jpg"
alt="Couverture du guide SEO Next.js complet"
width={1200}
height={630}
priority
/>
Lazy load, WebP, tailles responsives, moins de CLS (Core Web Vitals).
2. alt obligatoire
SEO + accessibilité (lecteurs d’écran).
Bon :
- ✅ « Exemple de code Metadata API Next.js »
- ✅ « Rich snippet d’article de blog dans Google »
Mauvais :
- ❌ « image »
- ❌ « screenshot.png »
- ❌ alt vide
Cas pratique — blog tech SEO complet
Structure type Next.js 15 App Router :
app/
├── layout.tsx # Root layout — config globale
├── page.tsx # Accueil
├── about/page.tsx # À propos
├── blog/
│ ├── page.tsx # Liste articles
│ └── [slug]/
│ ├── page.tsx # Détail article
│ └── opengraph-image.tsx # OG dynamique (optionnel)
├── sitemap.ts
└── robots.ts
1. Root Layout
// app/layout.tsx
import { Metadata } from 'next'
import StructuredData from '@/components/StructuredData'
export const metadata: Metadata = {
metadataBase: new URL('https://yourdomain.com'),
title: {
template: '%s | TechBlog',
default: 'TechBlog - Blog développement frontend',
},
description: 'Blog tech : Next.js, React, TypeScript',
keywords: ['Next.js', 'React', 'TypeScript', 'développement frontend', 'blog tech'],
authors: [{ name: 'Zhang San', url: 'https://yourdomain.com/about' }],
openGraph: {
type: 'website',
siteName: 'TechBlog',
locale: 'zh_CN',
},
twitter: {
card: 'summary_large_image',
site: '@yourusername',
},
}
const organizationData = {
'@context': 'https://schema.org',
'@type': 'Organization',
name: 'TechBlog',
url: 'https://yourdomain.com',
logo: 'https://yourdomain.com/logo.png',
sameAs: [
'https://twitter.com/yourusername',
'https://github.com/yourcompany',
],
}
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="zh-CN">
<body>
{children}
<StructuredData data={organizationData} />
</body>
</html>
)
}
2. Accueil — metadata statique
// app/page.tsx
import { Metadata } from 'next'
export const metadata: Metadata = {
title: {
absolute: 'TechBlog - Blog développement frontend',
},
description: 'Next.js, React, TypeScript : retours d\'expérience pour monter en compétences',
openGraph: {
title: 'TechBlog - Blog développement frontend',
description: 'Retours d\'expérience frontend',
url: 'https://yourdomain.com',
images: [
{
url: 'https://yourdomain.com/og-home.jpg',
width: 1200,
height: 630,
alt: 'Couverture accueil TechBlog',
},
],
},
}
export default function HomePage() {
return <div>Contenu accueil...</div>
}
3. Détail article — metadata dynamique + JSON-LD
// app/blog/[slug]/page.tsx
import { Metadata } from 'next'
import { notFound } from 'next/navigation'
import { getPostBySlug } from '@/lib/posts'
import StructuredData from '@/components/StructuredData'
export async function generateMetadata(
{ params }: { params: { slug: string } }
): Promise<Metadata> {
const post = await getPostBySlug(params.slug)
if (!post) return {}
return {
title: post.title,
description: post.excerpt,
keywords: post.tags,
authors: [{ name: post.author }],
openGraph: {
title: post.title,
description: post.excerpt,
url: `https://yourdomain.com/blog/${post.slug}`,
images: [post.coverImage],
type: 'article',
publishedTime: post.publishedAt,
authors: [post.author],
},
twitter: {
card: 'summary_large_image',
title: post.title,
description: post.excerpt,
images: [post.coverImage],
},
alternates: {
canonical: `https://yourdomain.com/blog/${post.slug}`,
},
}
}
export default async function BlogPostPage({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug)
if (!post) notFound()
const articleData = {
'@context': 'https://schema.org',
'@type': 'BlogPosting',
headline: post.title,
description: post.excerpt,
image: post.coverImage,
datePublished: post.publishedAt,
dateModified: post.updatedAt || post.publishedAt,
author: {
'@type': 'Person',
name: post.author,
},
publisher: {
'@type': 'Organization',
name: 'TechBlog',
logo: {
'@type': 'ImageObject',
url: 'https://yourdomain.com/logo.png',
},
},
mainEntityOfPage: {
'@type': 'WebPage',
'@id': `https://yourdomain.com/blog/${post.slug}`,
},
}
return (
<>
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
<StructuredData data={articleData} />
</>
)
}
4. Sitemap et Robots
// app/sitemap.ts
import { getAllPosts } from '@/lib/posts'
export default async function sitemap() {
const posts = await getAllPosts()
const baseUrl = 'https://yourdomain.com'
const blogUrls = posts.map((post) => ({
url: `${baseUrl}/blog/${post.slug}`,
lastModified: new Date(post.updatedAt),
changeFrequency: 'weekly' as const,
priority: 0.7,
}))
return [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: 'daily' as const,
priority: 1,
},
{
url: `${baseUrl}/about`,
lastModified: new Date(),
changeFrequency: 'monthly' as const,
priority: 0.8,
},
...blogUrls,
]
}
// app/robots.ts
export default function robots() {
return {
rules: {
userAgent: '*',
allow: '/',
disallow: ['/api', '/admin'],
},
sitemap: 'https://yourdomain.com/sitemap.xml',
}
}
Checklist post-déploiement
- Code source HTML — F12 →
<head>: title, description, OG OK - sitemap et robots —
/sitemap.xmlet/robots.txtaccessibles - Données structurées — Google Rich Results Test
- Partage social — Facebook Debugger, Twitter Card Validator
- Soumission — Search Console + Bing Webmaster Tools
Conclusion
Le SSR Next.js ≠ SEO automatique, mais Next.js 15 rend la configuration simple.
Récap :
- Metadata API : typée ;
metadatastatique,generateMetadatadynamique - JSON-LD : +20-30 % CTR ;
<Script>suffit - Open Graph / Twitter Cards : 1200x630 universel
- sitemap.ts / robots.ts : génération auto ; soumettez à Search Console
- Images :
next/image+ alt soignés → trafic Google Images
Ces réglages semblent lourds, ROI énorme. Trop de produits solides restent invisibles faute de SEO ; bien configurés, le trafic organique compense largement.
Le SEO est une science, pas de la magie. Checklist, validation outils, effets visibles en quelques mois.
N’attendez pas le « 0 » en Search Console. Ouvrez votre projet, une demi-journée suffit. Gardez ce guide sous la main.
Si utile, partagez-le — moins de pièges pour vos collègues dev.
Configuration SEO complète Next.js
Étapes complètes d'optimisation SEO : Metadata API, données structurées, sitemap et robots.txt
⏱️ Estimated time: 4 hr
- 1
Step 1: Configurer les metadata de base
Metadata API Next.js 15 :
• Exporter l'objet metadata dans layout.js ou page.js
• Configurer title, description, keywords
• Définir Open Graph et Twitter Cards
Exemple :
export const metadata = {
title: 'Titre de page',
description: 'Description de page',
openGraph: {
title: 'Titre OG',
description: 'Description OG',
images: ['/og-image.jpg']
}
} - 2
Step 2: Configurer les metadata dynamiques
Pour les routes dynamiques :
• Utiliser generateMetadata
• Générer les metadata selon les paramètres de route
• Support des fonctions async pour récupérer les données
Exemple :
export async function generateMetadata({ params }) {
const post = await getPost(params.id)
return {
title: post.title,
description: post.description
}
} - 3
Step 3: Ajouter des données structurées
Format JSON-LD :
• Balise script, type application/ld+json, contenu JSON (implémentation avec balise)
• Types Article, Product, FAQ, etc.
• Vocabulaire Schema.org
Exemple (corps JSON ; sur la page, JSON.stringify dans le script) :
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Titre de l'article"
}
Next.js : next/script ou équivalent pour sortir le JSON. - 4
Step 4: Configurer sitemap et robots.txt
Créer sitemap.ts :
• Exporter une fonction default retournant un tableau sitemap
• Inclure URL, lastModified, changeFrequency de chaque page
• Génération dynamique possible
Créer robots.ts :
• Configurer les crawlers autorisés/interdits
• Définir le chemin du sitemap
• Règles de crawl - 5
Step 5: Optimiser le SEO des images
Points clés :
• Composant next/image
• Texte alt significatif
• Dimensions (1200x630 pour OG)
• Formats WebP/AVIF
• Données structurées ImageObject - 6
Step 6: Valider et tester
Outils :
• Google Rich Results Test : valider les données structurées
• Facebook Sharing Debugger : tester les balises OG
• Twitter Card Validator : tester Twitter Cards
• Google Search Console : soumettre le sitemap et surveiller
Checklist :
• Title et description uniques par page
• Dimensions OG correctes (1200x630)
• Format des données structurées valide
• Sitemap soumis à Search Console
FAQ
Quel lien entre SSR et SEO ?
Metadata API vs composant Head ?
Comment configurer les metadata pour les routes dynamiques ?
Exemple :
export async function generateMetadata({ params }) {
const data = await getData(params.id)
return { title: data.title }
}
Quelle taille pour les images Open Graph ?
Les données structurées sont-elles obligatoires ?
Faut-il créer sitemap et robots.txt à la main ?
Combien de temps avant de voir les effets du SEO ?
Conseils :
1) Soumettre le sitemap à Google Search Console
2) Valider avec Google Rich Results Test
3) Surveiller Search Console
4) Maintenir le contenu à jour
12 min de lecture · Publié le: 19 déc. 2025 · Mis à jour le: 27 juil. 2026
Guide complet Next.js
Si vous arrivez depuis la recherche, le plus rapide est de passer à l’article précédent ou suivant de cette série.
Précédent
Core Web Vitals Next.js en pratique : guide complet LCP/FCP/CLS
Guide complet pour optimiser LCP, FCP et CLS dans Next.js et atteindre 90+ au Lighthouse. Plus de 10 exemples de code, pièges courants et astuces terrain.
Partie 27 sur 51
Suivant
Guide Next.js Sitemap et robots.txt : indexer rapidement votre site
Guide complet pour configurer Sitemap et robots.txt dans Next.js : trois méthodes de génération, pièges courants à éviter et intégration Google Search Console pour accélérer l'indexation.
Partie 29 sur 51



Commentaires
Connectez-vous avec GitHub pour laisser un commentaire