Kurs Next.js · Moduł 8: Deployment i produkcja

Generowanie map witryn i robots.txt

9 min czytania
W tej lekcji12

W świecie wyszukiwarek internetowych, dostępność treści i ich poprawna indeksacja stanowią fundament skutecznego SEO (Search Engine Optimization). Next.js oferuje zaawansowane mechanizmy i narzędzia, które ułatwiają generowanie map witryn (sitemaps) oraz plików robots.txt - dwóch kluczowych elementów dla wyszukiwarek takich jak Google, Bing czy Yahoo.

Znaczenie map witryn i pliku robots.txt

Mapa witryny (Sitemap)

Mapa witryny to plik XML, który zawiera listę wszystkich adresów URL twojej strony wraz z dodatkowymi metadanymi, takimi jak:

  • Data ostatniej modyfikacji strony
  • Częstotliwość zmian
  • Priorytet ważności w porównaniu z innymi stronami

Mapa witryny pomaga wyszukiwarkom w następujących aspektach:

  1. Odkrywanie zawartości - informuje o wszystkich stronach na Twojej witrynie
  2. Efektywne indeksowanie - pomaga zrozumieć strukturę witryny
  3. Priorytyzacja - wskazuje, które strony są najważniejsze
  4. Aktualność - informuje, kiedy strony były ostatnio modyfikowane

Plik robots.txt

Robots.txt to prosty plik tekstowy, który zawiera instrukcje dla robotów wyszukiwarek (crawlers). Główne funkcje robots.txt to:

  1. Kontrola dostępu - określa, które strony mogą być indeksowane, a które nie
  2. Zarządzanie ruchem botów - zapobiega przeciążeniu serwera przez ograniczenie dostępu
  3. Wskazywanie lokalizacji mapy witryny - informuje boty o lokalizacji pliku sitemap.xml

Ręczne generowanie mapy witryny w Next.js

Zacznijmy od najprostszego podejścia - ręcznego stworzenia mapy witryny jako statycznego pliku XML:

1// app/sitemap.xml/route.ts
2import { BASE_URL } from '@/lib/constants';
3import { getAllPostSlugs } from '@/lib/api';
4
5export async function GET() {
6  // Pobierz dynamiczne dane, np. adresy URL wszystkich postów bloga
7  const postSlugs = await getAllPostSlugs();
8
9  // Aktualna data dla lastmod
10  const date = new Date().toISOString();
11
12  // Tworzenie zawartości 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  // Zwróć odpowiedź z odpowiednim 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}

To podejście jest elastyczne i pozwala na pełną kontrolę nad zawartością mapy witryny, ale wymaga ręcznego zarządzania i aktualizacji.

Automatyczne generowanie mapy witryny za pomocą funkcji sitemap()

Next.js 13.4+ wprowadził specjalną funkcję sitemap(), która upraszcza generowanie map witryn. W tym podejściu definiujesz plik sitemap.ts lub sitemap.js w katalogu głównym aplikacji:

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  // Pobierz wszystkie produkty
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  // Pobierz wszystkie posty bloga
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  // Statyczne strony
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  // Połącz wszystkie wpisy
48  return [...staticPages, ...productEntries, ...blogEntries];
49}

Kiedy budujesz aplikację, Next.js automatycznie wygeneruje plik /sitemap.xml na podstawie zwracanych danych. Dodatkowe korzyści tego podejścia:

  • Wbudowane typowanie TypeScript - dzięki MetadataRoute.Sitemap
  • Automatyczna walidacja - Next.js sprawdza poprawność struktury danych
  • Integracja z systemem kompilacji - mapa witryny jest generowana podczas budowania

Generowanie mapy witryny dla dużych stron: podejście wieloplikowe

Dla większych stron z tysiącami URL, zaleca się tworzenie wielu map witryn i indeksu map witryn. Oto przykład implementacji:

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  // Logika generowania mapy statycznych stron...
32}
33
34// app/sitemaps/products-sitemap.xml/route.ts
35export async function GET() {
36  // Logika generowania mapy produktów...
37}
38
39// app/sitemaps/blog-sitemap.xml/route.ts
40export async function GET() {
41  // Logika generowania mapy blogów...
42}

To podejście jest skalowalne i pozwala każdej mapie witryny pozostać poniżej limitu 50,000 URL (zalecany maksymalny rozmiar pojedynczej mapy witryny).

Tworzenie pliku robots.txt

Ręczne tworzenie robots.txt

Najprostszym sposobem jest stworzenie pliku Route Handler dla 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}

Użycie funkcji robots() w Next.js

Next.js 13.4+ wprowadził również dedykowaną funkcję robots() do generowania pliku 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}

Podobnie jak w przypadku funkcji sitemap(), Next.js automatycznie wygeneruje plik /robots.txt na podstawie zwracanych danych, z wbudowaną walidacją i obsługą TypeScript.

Dynamiczne generowanie maps witryn i robots.txt

Dla bardziej zaawansowanych przypadków użycia, możesz tworzyć zależne od środowiska lub dynamiczne mapy witryn i pliki robots.txt:

1// app/robots.ts
2import { MetadataRoute } from 'next';
3
4export default function robots(): MetadataRoute.Robots {
5  // Różne zasady dla różnych środowisk
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}

W powyższym przykładzie, w środowisku deweloperskim robots.txt blokuje indeksację całej witryny, podczas gdy w produkcji pozwala na indeksację większości stron.

Weryfikacja i testowanie

Po wdrożeniu map witryn i pliku robots.txt, ważne jest ich przetestowanie, aby upewnić się, że działają poprawnie:

Narzędzia do testowania

  1. Google Search Console - dodaj swoją stronę i prześlij mapę witryny do weryfikacji
  2. Bing Webmaster Tools - podobnie jak Google, oferuje narzędzia do weryfikacji
  3. Narzędzia sprawdzania map witryn i Robots.txt:

Ręczna weryfikacja

Możesz także ręcznie sprawdzić swoje pliki, aby upewnić się, że zawierają oczekiwane dane:

  1. Odwiedź https://yourdomain.com/sitemap.xml w przeglądarce - powinien pokazać się poprawnie sformatowany plik XML
  2. Odwiedź https://yourdomain.com/robots.txt - powinien pokazać się poprawny plik tekstowy
  3. Sprawdź przykładowe URL z mapy witryny, aby upewnić się, że są one dostępne i zawierają oczekiwaną treść

Najlepsze praktyki

Dla map witryn

  1. Aktualizuj regularnie - generuj mapy witryn przy każdym wdrożeniu lub zmianie treści
  2. Uwzględniaj tylko kanoniczne URL - unikaj duplikatów i wersji alternatywnych tej samej treści
  3. Używaj dokładnych dat lastmod - nie generuj daty dynamicznie, jeśli treść się nie zmieniła
  4. Uwzględniaj tylko ważne strony - pomijaj strony administracyjne, logowania, koszyki itp.
  5. Uwzględniaj alternatywne wersje językowe - używaj elementu <xhtml:link rel="alternate"> dla wielojęzycznych stron

Dla pliku robots.txt

  1. Bądź selektywny z Disallow - blokuj tylko te ścieżki, które naprawdę nie powinny być indeksowane
  2. Używaj odpowiednich User-agent - rozważ różne zasady dla różnych robotów
  3. Dodaj link do mapy witryny - zawsze uwzględniaj odniesienie do sitemap.xml
  4. Komentuj swoje zasady - dodawaj komentarze wyjaśniające, dlaczego blokujesz określone ścieżki
  5. Testuj zmiany - weryfikuj, czy zmiany w robots.txt nie blokują ważnych stron

Automatyzacja aktualizacji map witryn

Dla stron, które często się zmieniają, rozważ automatyzację generowania map witryn:

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('Generowanie mapy witryny...');
8
9  // Pobierz wszystkie dynamiczne ścieżki
10  const dynamicPaths = await getAllDynamicPaths();
11
12  // Generuj 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  // Zapisz do pliku
31  const publicPath = path.join(process.cwd(), 'public');
32  fs.writeFileSync(path.join(publicPath, 'sitemap.xml'), sitemap);
33
34  console.log('Mapa witryny wygenerowana pomyślnie!');
35}
36
37generateSitemap().catch(console.error);

Skrypt ten można uruchamiać jako krok w procesie wdrażania lub poprzez zaplanowane zadanie.

Integracja z CMS

Jeśli korzystasz z CMS (Content Management System), możesz automatycznie generować mapy witryn na podstawie zawartości zarządzanej przez 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  // Pobierz wszystkie strony z 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  // Pobierz wszystkie posty z 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  // Połącz wszystkie wpisy
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}

Obsługa map witryn obrazów i wideo

Dla stron z dużą ilością obrazów lub wideo, warto rozważyć specjalne mapy witryn:

Mapa witryny obrazów

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}

Podsumowanie

Mapa witryny i plik robots.txt to kluczowe elementy każdej aplikacji Next.js, które chcą być widoczne w wynikach wyszukiwania. Next.js 13.4+ wprowadził dedykowane funkcje sitemap() i robots(), które znacznie upraszczają proces generowania tych plików, zapewniając jednocześnie wsparcie dla TypeScript i automatyczną walidację.

Pamiętaj, że skuteczne SEO to nie tylko techniczne aspekty jak mapy witryn, ale szerszy zestaw praktyk, w tym jakość treści, szybkość ładowania i dostępność. Generowanie map witryn i robots.txt to jednak ważny krok w kierunku zwiększenia widoczności twojej aplikacji Next.js w wyszukiwarkach internetowych.

Kod do tej lekcji: App.tsx
1import React, { useState } from 'react';
2
3// Demo: Generowanie map witryn (sitemap) i 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' }}>Generuj mapy witryn i robots.txt dla 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="/nowa-strona" 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' }}>+ Dodaj</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' }}>Wygenerowany sitemap.xml:</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' }}>Kod 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 }}>Konfiguracja 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;

Widzisz błąd w tej lekcji?

Zadania praktyczne w grze

  • Układanie w pionie

    Ułóż elementy w prawidłowej kolejności: import { headers } → from 'next/headers'

  • Edytor kodu

    Utwórz next.config.js z optymalizacjami production: compression, webpack bundle analyzer, image optimization

Przydatne artykuły