Cours Next.js · Module 8: Déploiement et production
Générer des sitemaps et le fichier robots.txt
Dans cette leçon12
Dans le monde des moteurs de recherche, l'accessibilité du contenu et son indexation correcte sont les fondations d'un SEO (Search Engine Optimization) efficace. Next.js propose des mécanismes et des outils avancés qui facilitent la génération de sitemaps et de fichiers robots.txt - deux éléments clés pour les moteurs de recherche comme Google, Bing ou Yahoo.
L'importance des sitemaps et du fichier robots.txt
Le sitemap (plan du site)
Un sitemap est un fichier XML qui contient la liste de toutes les URL de ta page ainsi que des métadonnées supplémentaires, comme :
- La date de dernière modification des pages
- La fréquence de changement
- La priorité d'importance par rapport aux autres pages
Un sitemap aide les moteurs de recherche sur les aspects suivants :
- Découverte du contenu - informe sur toutes les pages de ton site
- Indexation efficace - aide à comprendre la structure du site
- Priorisation - indique quelles pages sont les plus importantes
- Fraîcheur - informe sur la date de dernière modification des pages
Le fichier robots.txt
Robots.txt est un simple fichier texte qui contient des instructions pour les robots des moteurs de recherche (crawlers). Les principales fonctions de robots.txt sont :
- Contrôle d'accès - détermine quelles pages peuvent être indexées et lesquelles ne le peuvent pas
- Gestion du trafic des bots - évite de surcharger le serveur en limitant l'accès
- Indication de l'emplacement du sitemap - informe les bots de l'emplacement du fichier sitemap.xml
Générer manuellement un sitemap dans Next.js
Commençons par l'approche la plus simple - créer manuellement un sitemap sous forme de fichier XML statique :
1// app/sitemap.xml/route.ts
2import { BASE_URL } from '@/lib/constants';
3import { getAllPostSlugs } from '@/lib/api';
4
5export async function GET() {
6 // Récupère les données dynamiques, par ex. les URL de tous les articles de blog
7 const postSlugs = await getAllPostSlugs();
8
9 // Date actuelle pour lastmod
10 const date = new Date().toISOString();
11
12 // Création du contenu XML
13 const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
14 <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
15 <url>
16 <loc>${BASE_URL}</loc>
17 <lastmod>${date}</lastmod>
18 <changefreq>daily</changefreq>
19 <priority>1.0</priority>
20 </url>
21 <url>
22 <loc>${BASE_URL}/about</loc>
23 <lastmod>${date}</lastmod>
24 <changefreq>monthly</changefreq>
25 <priority>0.8</priority>
26 </url>
27 <url>
28 <loc>${BASE_URL}/products</loc>
29 <lastmod>${date}</lastmod>
30 <changefreq>weekly</changefreq>
31 <priority>0.9</priority>
32 </url>
33 ${postSlugs.map(slug => `
34 <url>
35 <loc>${BASE_URL}/blog/${slug}</loc>
36 <lastmod>${date}</lastmod>
37 <changefreq>weekly</changefreq>
38 <priority>0.7</priority>
39 </url>
40 `).join('')}
41 </urlset>`;
42
43 // Renvoie la réponse avec le bon Content-Type
44 return new Response(sitemap, {
45 headers: {
46 'Content-Type': 'application/xml',
47 'Cache-Control': 'public, max-age=3600, s-maxage=3600'
48 }
49 });
50}Cette approche est flexible et permet un contrôle total du contenu du sitemap, mais demande une gestion et des mises à jour manuelles.
Générer automatiquement un sitemap avec la fonction sitemap()
Next.js 13.4+ a introduit une fonction spéciale sitemap() qui simplifie la génération du sitemap. Dans cette approche, tu définis un fichier sitemap.ts ou sitemap.js dans le répertoire principal de l'application :
1// app/sitemap.ts
2import { MetadataRoute } from 'next';
3import { getAllProducts } from '@/lib/products';
4import { getAllPosts } from '@/lib/blog';
5
6export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
7 // Récupère tous les produits
8 const products = await getAllProducts();
9 const productEntries = products.map(product => ({
10 url: `https://example.com/products/${product.slug}`,
11 lastModified: new Date(product.updatedAt),
12 changeFrequency: 'weekly' as const,
13 priority: 0.8,
14 }));
15
16 // Récupère tous les articles de blog
17 const posts = await getAllPosts();
18 const blogEntries = posts.map(post => ({
19 url: `https://example.com/blog/${post.slug}`,
20 lastModified: new Date(post.publishedAt),
21 changeFrequency: 'monthly' as const,
22 priority: 0.7,
23 }));
24
25 // Pages statiques
26 const staticPages = [
27 {
28 url: 'https://example.com',
29 lastModified: new Date(),
30 changeFrequency: 'daily' as const,
31 priority: 1.0,
32 },
33 {
34 url: 'https://example.com/about',
35 lastModified: new Date(),
36 changeFrequency: 'monthly' as const,
37 priority: 0.5,
38 },
39 {
40 url: 'https://example.com/contact',
41 lastModified: new Date(),
42 changeFrequency: 'yearly' as const,
43 priority: 0.5,
44 },
45 ];
46
47 // Combine toutes les entrées
48 return [...staticPages, ...productEntries, ...blogEntries];
49}Quand tu builds l'application, Next.js génère automatiquement le fichier /sitemap.xml à partir des données renvoyées. Avantages supplémentaires de cette approche :
- Typage TypeScript intégré - grâce à
MetadataRoute.Sitemap - Validation automatique - Next.js valide la justesse de la structure des données
- Intégration au système de compilation - le sitemap est généré pendant le build
Générer un sitemap pour les grands sites : approche multi-fichiers
Pour les sites plus grands avec des milliers d'URL, il est recommandé de créer plusieurs sitemaps et un index de sitemaps. Voici un exemple d'implémentation :
1// app/sitemap-index.xml/route.ts
2export async function GET() {
3 const baseUrl = 'https://example.com';
4
5 const sitemapIndex = `<?xml version="1.0" encoding="UTF-8"?>
6 <sitemapindex xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
7 <sitemap>
8 <loc>${baseUrl}/sitemaps/static-sitemap.xml</loc>
9 <lastmod>${new Date().toISOString()}</lastmod>
10 </sitemap>
11 <sitemap>
12 <loc>${baseUrl}/sitemaps/products-sitemap.xml</loc>
13 <lastmod>${new Date().toISOString()}</lastmod>
14 </sitemap>
15 <sitemap>
16 <loc>${baseUrl}/sitemaps/blog-sitemap.xml</loc>
17 <lastmod>${new Date().toISOString()}</lastmod>
18 </sitemap>
19 </sitemapindex>`;
20
21 return new Response(sitemapIndex, {
22 headers: {
23 'Content-Type': 'application/xml',
24 'Cache-Control': 'public, max-age=3600, s-maxage=3600'
25 }
26 });
27}
28
29// app/sitemaps/static-sitemap.xml/route.ts
30export async function GET() {
31 // Logique de génération du sitemap des pages statiques...
32}
33
34// app/sitemaps/products-sitemap.xml/route.ts
35export async function GET() {
36 // Logique de génération du sitemap des produits...
37}
38
39// app/sitemaps/blog-sitemap.xml/route.ts
40export async function GET() {
41 // Logique de génération du sitemap du blog...
42}Cette approche est scalable et permet à chaque sitemap de rester sous la limite de 50 000 URL (la taille maximale recommandée pour un seul sitemap).
Créer le fichier robots.txt
Création manuelle de robots.txt
La façon la plus simple est de créer un Route Handler pour robots.txt :
1// app/robots.txt/route.ts
2export async function GET() {
3 const robotsTxt = `# https://www.robotstxt.org/robotstxt.html
4User-agent: *
5Allow: /
6
7Disallow: /admin/
8Disallow: /api/
9Disallow: /internal/
10
11Sitemap: https://example.com/sitemap.xml`;
12
13 return new Response(robotsTxt, {
14 headers: {
15 'Content-Type': 'text/plain',
16 'Cache-Control': 'public, max-age=3600, s-maxage=3600'
17 }
18 });
19}Utiliser la fonction robots() dans Next.js
Next.js 13.4+ a aussi introduit une fonction dédiée robots() pour générer le fichier robots.txt :
1// app/robots.ts
2import { MetadataRoute } from 'next';
3
4export default function robots(): MetadataRoute.Robots {
5 return {
6 rules: {
7 userAgent: '*',
8 allow: '/',
9 disallow: ['/admin/', '/api/', '/internal/'],
10 },
11 sitemap: 'https://example.com/sitemap.xml',
12 };
13}De la même façon que la fonction sitemap(), Next.js génère automatiquement le fichier /robots.txt à partir des données renvoyées, avec une validation intégrée et le support de TypeScript.
Génération dynamique des sitemaps et de robots.txt
Pour des cas d'usage plus avancés, tu peux créer des sitemaps et des fichiers robots.txt dynamiques ou dépendants de l'environnement :
1// app/robots.ts
2import { MetadataRoute } from 'next';
3
4export default function robots(): MetadataRoute.Robots {
5 // Règles différentes pour différents environnements
6 const isProduction = process.env.NODE_ENV === 'production';
7
8 if (!isProduction) {
9 return {
10 rules: {
11 userAgent: '*',
12 disallow: '/',
13 },
14 };
15 }
16
17 return {
18 rules: {
19 userAgent: '*',
20 allow: '/',
21 disallow: ['/admin/', '/api/'],
22 },
23 sitemap: 'https://example.com/sitemap.xml',
24 };
25}Dans l'exemple ci-dessus, dans l'environnement de développement, robots.txt bloque l'indexation de tout le site, tandis qu'en production il autorise l'indexation de la plupart des pages.
Vérification et tests
Après avoir déployé les sitemaps et le fichier robots.txt, il est important de les tester pour s'assurer qu'ils fonctionnent correctement :
Outils de test
- Google Search Console - ajoute ta page et soumets le sitemap pour vérification
- Bing Webmaster Tools - comme Google, propose des outils de vérification
- Outils de vérification de sitemap et robots.txt :
Vérification manuelle
Tu peux aussi vérifier manuellement tes fichiers pour t'assurer qu'ils contiennent les données attendues :
- Visite
https://yourdomain.com/sitemap.xmldans le navigateur - un fichier XML correctement formaté devrait apparaître - Visite
https://yourdomain.com/robots.txt- un fichier texte correct devrait apparaître - Vérifie quelques URL du sitemap pour t'assurer qu'elles sont accessibles et contiennent le contenu attendu
Bonnes pratiques
Pour les sitemaps
- Mets à jour régulièrement - génère les sitemaps à chaque déploiement ou changement de contenu
- N'inclus que des URL canoniques - évite les doublons et les versions alternatives du même contenu
- Utilise des dates lastmod précises - ne génère pas la date dynamiquement si le contenu n'a pas changé
- N'inclus que les pages importantes - ignore les pages d'administration, de connexion, de panier, etc.
- Inclus les versions linguistiques alternatives - utilise l'élément
<xhtml:link rel="alternate">pour les pages multilingues
Pour le fichier robots.txt
- Sois sélectif avec Disallow - ne bloque que les chemins qui ne devraient vraiment pas être indexés
- Utilise le bon User-agent - envisage des règles différentes pour différents robots
- Ajoute un lien vers le sitemap - inclus toujours une référence vers sitemap.xml
- Commente tes règles - ajoute des commentaires expliquant pourquoi tu bloques certains chemins
- Teste les changements - vérifie que les modifications dans robots.txt ne bloquent pas des pages importantes
Automatiser la mise à jour des sitemaps
Pour les pages qui changent fréquemment, envisage d'automatiser la génération du sitemap :
1// scripts/generate-sitemap.js
2const fs = require('fs');
3const path = require('path');
4const { getAllDynamicPaths } = require('../lib/api');
5
6async function generateSitemap() {
7 console.log('Génération du sitemap...');
8
9 // Récupère tous les paths dynamiques
10 const dynamicPaths = await getAllDynamicPaths();
11
12 // Génère le XML
13 const date = new Date().toISOString();
14 const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
15<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
16 <url>
17 <loc>https://example.com</loc>
18 <lastmod>${date}</lastmod>
19 <priority>1.0</priority>
20 </url>
21 ${dynamicPaths.map(path => `
22 <url>
23 <loc>https://example.com${path}</loc>
24 <lastmod>${date}</lastmod>
25 <priority>0.8</priority>
26 </url>
27 `).join('')}
28</urlset>`;
29
30 // Enregistre dans le fichier
31 const publicPath = path.join(process.cwd(), 'public');
32 fs.writeFileSync(path.join(publicPath, 'sitemap.xml'), sitemap);
33
34 console.log('Sitemap generated successfully!');
35}
36
37generateSitemap().catch(console.error);Ce script peut être lancé comme une étape du processus de déploiement ou via une tâche planifiée.
Intégration avec un CMS
Si tu utilises un CMS (Content Management System), tu peux générer automatiquement les sitemaps à partir du contenu géré par le CMS :
1// app/sitemap.ts
2import { MetadataRoute } from 'next';
3import { getCmsClient } from '@/lib/cms';
4
5export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
6 const cms = getCmsClient();
7
8 // Récupère toutes les pages du CMS
9 const pages = await cms.getPages();
10 const pageEntries = pages.map(page => ({
11 url: `https://example.com/${page.slug}`,
12 lastModified: new Date(page.updatedAt),
13 changeFrequency: 'weekly' as const,
14 priority: 0.8,
15 }));
16
17 // Récupère tous les articles du CMS
18 const posts = await cms.getPosts();
19 const postEntries = posts.map(post => ({
20 url: `https://example.com/blog/${post.slug}`,
21 lastModified: new Date(post.updatedAt),
22 changeFrequency: 'weekly' as const,
23 priority: 0.7,
24 }));
25
26 // Combine toutes les entrées
27 return [
28 {
29 url: 'https://example.com',
30 lastModified: new Date(),
31 changeFrequency: 'daily' as const,
32 priority: 1.0,
33 },
34 ...pageEntries,
35 ...postEntries,
36 ];
37}Gérer les sitemaps d'images et de vidéos
Pour les sites avec beaucoup d'images ou de vidéos, il vaut la peine d'envisager des sitemaps spécialisés :
Sitemap d'images
1// app/image-sitemap.xml/route.ts
2import { getAllProductImages } from '@/lib/products';
3
4export async function GET() {
5 const images = await getAllProductImages();
6
7 const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
8 <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
9 xmlns:image="http://www.google.com/schemas/sitemap-image/1.1">
10 ${images.map(image => `
11 <url>
12 <loc>https://example.com/products/${image.productSlug}</loc>
13 <image:image>
14 <image:loc>https://example.com${image.url}</image:loc>
15 <image:title>${image.title}</image:title>
16 <image:caption>${image.caption}</image:caption>
17 </image:image>
18 </url>
19 `).join('')}
20 </urlset>`;
21
22 return new Response(sitemap, {
23 headers: {
24 'Content-Type': 'application/xml',
25 'Cache-Control': 'public, max-age=3600, s-maxage=3600'
26 }
27 });
28}Résumé
Le sitemap et robots.txt sont des éléments clés de toute application Next.js qui veut être visible dans les résultats de recherche. Next.js 13.4+ a introduit les fonctions dédiées sitemap() et robots() qui simplifient considérablement le processus de génération de ces fichiers, tout en offrant le support de TypeScript et une validation automatique.
N'oublie pas qu'un SEO efficace, ce ne sont pas seulement des aspects techniques comme les sitemaps, mais un ensemble plus large de pratiques, dont la qualité du contenu, la vitesse de chargement et l'accessibilité. Générer des sitemaps et robots.txt est cependant une étape importante pour augmenter la visibilité de ton application Next.js dans les moteurs de recherche.
Code de cette leçon : App.tsx
1import React, { useState } from 'react';
2
3// Demo : Génération de sitemaps et de robots.txt
4interface SitemapEntry {
5 url: string;
6 lastModified: string;
7 changeFrequency: string;
8 priority: number;
9}
10
11function SitemapGenerator() {
12 const [pages, setPages] = useState<SitemapEntry[]>([
13 { url: 'https://example.com', lastModified: '2024-01-15', changeFrequency: 'daily', priority: 1.0 },
14 { url: 'https://example.com/about', lastModified: '2024-01-10', changeFrequency: 'monthly', priority: 0.8 },
15 { url: 'https://example.com/blog', lastModified: '2024-01-14', changeFrequency: 'weekly', priority: 0.9 },
16 { url: 'https://example.com/contact', lastModified: '2024-01-01', changeFrequency: 'yearly', priority: 0.5 },
17 ]);
18 const [showSitemap, setShowSitemap] = useState(false);
19 const [showRobots, setShowRobots] = useState(false);
20 const [newUrl, setNewUrl] = useState('');
21 const [newPriority, setNewPriority] = useState(0.5);
22
23 const [robotsConfig, setRobotsConfig] = useState({
24 allowAll: true,
25 disallowAdmin: true,
26 disallowApi: true,
27 sitemapUrl: 'https://example.com/sitemap.xml',
28 });
29
30 const addPage = () => {
31 if (!newUrl.trim()) return;
32 setPages(prev => [...prev, {
33 url: newUrl.startsWith('http') ? newUrl : 'https://example.com' + newUrl,
34 lastModified: new Date().toISOString().split('T')[0],
35 changeFrequency: 'weekly',
36 priority: newPriority,
37 }]);
38 setNewUrl('');
39 };
40
41 const removePage = (url: string) => setPages(prev => prev.filter(p => p.url !== url));
42
43 const sitemapXml = `<?xml version="1.0" encoding="UTF-8"?>
44<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
45${pages.map(p => ` <url>
46 <loc>${p.url}</loc>
47 <lastmod>${p.lastModified}</lastmod>
48 <changefreq>${p.changeFrequency}</changefreq>
49 <priority>${p.priority}</priority>
50 </url>`).join('\n')}
51</urlset>`;
52
53 const robotsTxt = `User-agent: *
54${robotsConfig.allowAll ? 'Allow: /' : 'Disallow: /'}
55${robotsConfig.disallowAdmin ? 'Disallow: /admin\nDisallow: /admin/*' : ''}
56${robotsConfig.disallowApi ? 'Disallow: /api\nDisallow: /api/*' : ''}
57
58Sitemap: ${robotsConfig.sitemapUrl}`;
59
60 const nextjsCode = `// app/sitemap.ts
61import { MetadataRoute } from 'next';
62
63export default function sitemap(): MetadataRoute.Sitemap {
64 return [
65${pages.map(p => ` { url: '${p.url}', lastModified: '${p.lastModified}', changeFrequency: '${p.changeFrequency}', priority: ${p.priority} },`).join('\n')}
66 ];
67}`;
68
69 return (
70 <div style={{ background: '#0f0f23', minHeight: '100vh', padding: '20px', color: '#e0e0e0', fontFamily: 'system-ui' }}>
71 <h1 style={{ color: '#64ffda', marginBottom: '8px' }}>Sitemap & robots.txt Generator</h1>
72 <p style={{ color: '#8892b0', marginBottom: '24px' }}>Génère des sitemaps et des robots.txt pour Next.js</p>
73
74 <div style={{ display: 'flex', gap: '12px', marginBottom: '20px', flexWrap: 'wrap' }}>
75 <input value={newUrl} onChange={e => setNewUrl(e.target.value)} placeholder="/nouvelle-page" style={{ padding: '8px 12px', borderRadius: '6px', background: '#1a1a2e', color: '#fff', border: '1px solid #333', flex: 1, minWidth: '200px' }} />
76 <input type="range" min="0" max="1" step="0.1" value={newPriority} onChange={e => setNewPriority(parseFloat(e.target.value))} style={{ width: '100px' }} />
77 <span style={{ color: '#8892b0', fontSize: '14px' }}>Priority: {newPriority}</span>
78 <button onClick={addPage} style={{ padding: '8px 16px', borderRadius: '6px', background: '#64ffda', color: '#0f0f23', border: 'none', cursor: 'pointer', fontWeight: 'bold' }}>+ Ajouter</button>
79 </div>
80
81 <div style={{ display: 'grid', gap: '8px', marginBottom: '24px' }}>
82 {pages.map(p => (
83 <div key={p.url} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', padding: '10px 16px', background: '#1a1a2e', borderRadius: '8px', border: '1px solid #333' }}>
84 <div>
85 <code style={{ color: '#64ffda' }}>{p.url}</code>
86 <div style={{ fontSize: '12px', color: '#8892b0' }}>
87 Priority: {p.priority} | Freq: {p.changeFrequency} | Modified: {p.lastModified}
88 </div>
89 </div>
90 <button onClick={() => removePage(p.url)} style={{ background: '#ef4444', color: '#fff', border: 'none', borderRadius: '4px', padding: '4px 8px', cursor: 'pointer', fontSize: '12px' }}>X</button>
91 </div>
92 ))}
93 </div>
94
95 <div style={{ display: 'flex', gap: '8px', marginBottom: '16px' }}>
96 <button onClick={() => { setShowSitemap(!showSitemap); setShowRobots(false); }} style={{ padding: '10px 20px', borderRadius: '8px', background: showSitemap ? '#64ffda' : '#1a1a2e', color: showSitemap ? '#0f0f23' : '#8892b0', border: 'none', cursor: 'pointer', fontWeight: 'bold' }}>sitemap.xml</button>
97 <button onClick={() => { setShowRobots(!showRobots); setShowSitemap(false); }} style={{ padding: '10px 20px', borderRadius: '8px', background: showRobots ? '#64ffda' : '#1a1a2e', color: showRobots ? '#0f0f23' : '#8892b0', border: 'none', cursor: 'pointer', fontWeight: 'bold' }}>robots.txt</button>
98 </div>
99
100 {showSitemap && (
101 <div>
102 <h3 style={{ color: '#f59e0b', fontSize: '14px' }}>sitemap.xml généré :</h3>
103 <pre style={{ background: '#1a1a2e', padding: '16px', borderRadius: '8px', fontSize: '12px', overflow: 'auto', border: '1px solid #333', color: '#e0e0e0', whiteSpace: 'pre-wrap' }}>{sitemapXml}</pre>
104 <h3 style={{ color: '#64ffda', fontSize: '14px', marginTop: '16px' }}>Code Next.js (app/sitemap.ts) :</h3>
105 <pre style={{ background: '#1a1a2e', padding: '16px', borderRadius: '8px', fontSize: '12px', overflow: 'auto', border: '1px solid #333', color: '#64ffda', whiteSpace: 'pre-wrap' }}>{nextjsCode}</pre>
106 </div>
107 )}
108
109 {showRobots && (
110 <div>
111 <div style={{ background: '#1a1a2e', padding: '16px', borderRadius: '8px', border: '1px solid #333', marginBottom: '16px' }}>
112 <h3 style={{ color: '#f59e0b', fontSize: '14px', marginTop: 0 }}>Configuration de robots.txt :</h3>
113 {[
114 { key: 'allowAll', label: 'Allow all pages' },
115 { key: 'disallowAdmin', label: 'Disallow /admin' },
116 { key: 'disallowApi', label: 'Disallow /api' },
117 ].map(opt => (
118 <label key={opt.key} style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px', cursor: 'pointer' }}>
119 <input type="checkbox" checked={(robotsConfig as any)[opt.key]} onChange={e => setRobotsConfig(prev => ({ ...prev, [opt.key]: e.target.checked }))} />
120 {opt.label}
121 </label>
122 ))}
123 </div>
124 <pre style={{ background: '#1a1a2e', padding: '16px', borderRadius: '8px', fontSize: '12px', overflow: 'auto', border: '1px solid #333', color: '#e0e0e0', whiteSpace: 'pre-wrap' }}>{robotsTxt}</pre>
125 </div>
126 )}
127 </div>
128 );
129}
130
131export default SitemapGenerator;Tu as repéré une erreur dans cette leçon ?
Tâches pratiques dans le jeu
- Tri vertical
Place les éléments dans le bon ordre : import { headers } → from 'next/headers'
- Éditeur de code
Crée next.config.js avec des optimisations de production : compression, webpack bundle analyzer, optimisation des images