Kurs Next.js · Moduł 8: Deployment i produkcja
Generowanie map witryn i robots.txt
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:
- Odkrywanie zawartości - informuje o wszystkich stronach na Twojej witrynie
- Efektywne indeksowanie - pomaga zrozumieć strukturę witryny
- Priorytyzacja - wskazuje, które strony są najważniejsze
- 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:
- Kontrola dostępu - określa, które strony mogą być indeksowane, a które nie
- Zarządzanie ruchem botów - zapobiega przeciążeniu serwera przez ograniczenie dostępu
- 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
- Google Search Console - dodaj swoją stronę i prześlij mapę witryny do weryfikacji
- Bing Webmaster Tools - podobnie jak Google, oferuje narzędzia do weryfikacji
- 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:
- Odwiedź
https://yourdomain.com/sitemap.xmlw przeglądarce - powinien pokazać się poprawnie sformatowany plik XML - Odwiedź
https://yourdomain.com/robots.txt- powinien pokazać się poprawny plik tekstowy - Sprawdź przykładowe URL z mapy witryny, aby upewnić się, że są one dostępne i zawierają oczekiwaną treść
Najlepsze praktyki
Dla map witryn
- Aktualizuj regularnie - generuj mapy witryn przy każdym wdrożeniu lub zmianie treści
- Uwzględniaj tylko kanoniczne URL - unikaj duplikatów i wersji alternatywnych tej samej treści
- Używaj dokładnych dat lastmod - nie generuj daty dynamicznie, jeśli treść się nie zmieniła
- Uwzględniaj tylko ważne strony - pomijaj strony administracyjne, logowania, koszyki itp.
- Uwzględniaj alternatywne wersje językowe - używaj elementu
<xhtml:link rel="alternate">dla wielojęzycznych stron
Dla pliku robots.txt
- Bądź selektywny z Disallow - blokuj tylko te ścieżki, które naprawdę nie powinny być indeksowane
- Używaj odpowiednich User-agent - rozważ różne zasady dla różnych robotów
- Dodaj link do mapy witryny - zawsze uwzględniaj odniesienie do sitemap.xml
- Komentuj swoje zasady - dodawaj komentarze wyjaśniające, dlaczego blokujesz określone ścieżki
- 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