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.
Mapa witryny to plik XML, który zawiera listę wszystkich adresów URL twojej strony wraz z dodatkowymi metadanymi, takimi jak:
Mapa witryny pomaga wyszukiwarkom w następujących aspektach:
Robots.txt to prosty plik tekstowy, który zawiera instrukcje dla robotów wyszukiwarek (crawlers). Główne funkcje robots.txt to:
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.
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:MetadataRoute.SitemapDla 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).
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}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.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.
Po wdrożeniu map witryn i pliku robots.txt, ważne jest ich przetestowanie, aby upewnić się, że działają poprawnie:
Możesz także ręcznie sprawdzić swoje pliki, aby upewnić się, że zawierają oczekiwane dane:
https://yourdomain.com/sitemap.xml w przeglądarce - powinien pokazać się poprawnie sformatowany plik XMLhttps://yourdomain.com/robots.txt - powinien pokazać się poprawny plik tekstowy<xhtml:link rel="alternate"> dla wielojęzycznych stronDla 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.
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}Dla stron z dużą ilością obrazów lub wideo, warto rozważyć specjalne mapy witryn:
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}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.