Cours Next.js · Module 8: Déploiement et production

Générer des sitemaps et le fichier robots.txt

10 min de lecture
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 :

  1. Découverte du contenu - informe sur toutes les pages de ton site
  2. Indexation efficace - aide à comprendre la structure du site
  3. Priorisation - indique quelles pages sont les plus importantes
  4. 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 :

  1. Contrôle d'accès - détermine quelles pages peuvent être indexées et lesquelles ne le peuvent pas
  2. Gestion du trafic des bots - évite de surcharger le serveur en limitant l'accès
  3. 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

  1. Google Search Console - ajoute ta page et soumets le sitemap pour vérification
  2. Bing Webmaster Tools - comme Google, propose des outils de vérification
  3. 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 :

  1. Visite https://yourdomain.com/sitemap.xml dans le navigateur - un fichier XML correctement formaté devrait apparaître
  2. Visite https://yourdomain.com/robots.txt - un fichier texte correct devrait apparaître
  3. Vérifie quelques URL du sitemap pour t'assurer qu'elles sont accessibles et contiennent le contenu attendu

Bonnes pratiques

Pour les sitemaps

  1. Mets à jour régulièrement - génère les sitemaps à chaque déploiement ou changement de contenu
  2. N'inclus que des URL canoniques - évite les doublons et les versions alternatives du même contenu
  3. Utilise des dates lastmod précises - ne génère pas la date dynamiquement si le contenu n'a pas changé
  4. N'inclus que les pages importantes - ignore les pages d'administration, de connexion, de panier, etc.
  5. Inclus les versions linguistiques alternatives - utilise l'élément <xhtml:link rel="alternate"> pour les pages multilingues

Pour le fichier robots.txt

  1. Sois sélectif avec Disallow - ne bloque que les chemins qui ne devraient vraiment pas être indexés
  2. Utilise le bon User-agent - envisage des règles différentes pour différents robots
  3. Ajoute un lien vers le sitemap - inclus toujours une référence vers sitemap.xml
  4. Commente tes règles - ajoute des commentaires expliquant pourquoi tu bloques certains chemins
  5. 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

Articles utiles