Przejdź do treści

Next.js Sitemap i robots.txt — automatyczna generacja z App Routera

Sitemap i robots.txt w Next.js App Router bez zewnętrznych paczek. Dynamiczne sitemaps, lastmod i jak nie wyeksponować staging przez przypadek.

Maciej Sala

Founder StriveLab

4 min czytaniaOpublikowano 10 kwietnia 2026 (Aktualizacja 25 maja 2026)

Dlaczego sitemap i robots.txt są ważne w Next.js?

.xml informuje wyszukiwarki o wszystkich stronach w serwisie — ich lokalizacji, dacie ostatniej modyfikacji i opcjonalnych wskazówkach, a plik kontroluje, które ścieżki crawlery mogą swobodnie odwiedzać. Oba generujesz natywnym konwencji plików Next.js. Choć żaden z nich nie gwarantuje indeksacji ani jej blokady, są fundamentem technicznego .

Natywna sitemap w Next.js App Router

Statyczna sitemap w app/sitemap.ts

Najprostszym sposobem jest eksport tablicy obiektów z pliku sitemap.ts:

Code
// app/sitemap.ts
import type { MetadataRoute } from 'next'
 
export default function sitemap(): MetadataRoute.Sitemap {
  return [
    {
      url: 'https://example.com',
      lastModified: new Date('2025-01-15'),
      changeFrequency: 'monthly',
      priority: 1,
    },
    {
      url: 'https://example.com/uslugi',
      lastModified: new Date('2025-01-15'),
      changeFrequency: 'monthly',
      priority: 0.9,
    },
    {
      url: 'https://example.com/blog',
      lastModified: new Date('2025-03-01'),
      changeFrequency: 'weekly',
      priority: 0.8,
    },
    {
      url: 'https://example.com/kontakt',
      lastModified: new Date('2025-01-15'),
      changeFrequency: 'yearly',
      priority: 0.5,
    },
  ]
}

Next.js generuje /sitemap.xml automatycznie.

Pułapka: lastModified: new Date() dla stron statycznych. Użycie new Date() (aktualny czas buildu) dla stron, które rzadko się zmieniają, spowoduje, że przy każdym deployu Google zobaczy nową datę modyfikacji. Może to zmylić crawlera i zmarnować . Dla stron statycznych używaj konkretnej daty lub pomijaj pole lastModified całkowicie. Dla treści z bazy danych pobieraj faktyczne pole updatedAt z rekordu.

changeFrequency i priority Google oficjalnie deklaruje, że oba pola traktuje tylko jako wskazówki i w praktyce je ignoruje. Z kolei inne wyszukiwarki (Bing, Yandex) mogą je uwzględniać, więc warto je wypełniać, ale nie skupiaj się na ich optymalizacji, kosztem innych działań SEO.

Dynamiczna sitemap z wpisami z CMS lub bazy

Code
// app/sitemap.ts
import type { MetadataRoute } from 'next'
import { db } from '@/lib/db'
 
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const baseUrl = 'https://example.com'
 
  // Statyczne strony — konkretna data, nie new Date()
  const staticPages: MetadataRoute.Sitemap = [
    {
      url: baseUrl,
      lastModified: new Date('2025-01-15'),
      changeFrequency: 'monthly',
      priority: 1,
    },
    {
      url: `${baseUrl}/uslugi`,
      lastModified: new Date('2025-01-15'),
      changeFrequency: 'monthly',
      priority: 0.9,
    },
    {
      url: `${baseUrl}/kontakt`,
      lastModified: new Date('2025-01-15'),
      changeFrequency: 'yearly',
      priority: 0.5,
    },
  ]
 
  // Dynamiczne strony — posty blogowe
  const posts = await db.post.findMany({
    where: { published: true },
    select: { slug: true, updatedAt: true },
    orderBy: { updatedAt: 'desc' },
  })
 
  const blogPages: MetadataRoute.Sitemap = posts.map((post) => ({
    url: `${baseUrl}/blog/${post.slug}`,
    lastModified: post.updatedAt, // faktyczna data z bazy — poprawne
    changeFrequency: 'monthly' as const,
    priority: 0.7,
  }))
 
  return [...staticPages, ...blogPages]
}

Wielojęzyczna sitemap z hreflang

Natywne API obsługuje pole alternates, służy do deklarowania wersji językowych strony, co Google wymaga przy implementacji hreflang:

Code
// app/sitemap.ts
import type { MetadataRoute } from 'next'
 
export default function sitemap(): MetadataRoute.Sitemap {
  const baseUrl = 'https://example.com'
 
  return [
    {
      url: baseUrl,
      lastModified: new Date('2025-01-15'),
      changeFrequency: 'monthly',
      priority: 1,
      alternates: {
        languages: {
          pl: baseUrl,
          en: `${baseUrl}/en`,
        },
      },
    },
    {
      url: `${baseUrl}/blog/jak-zbudowac-api`,
      lastModified: new Date('2025-02-10'),
      changeFrequency: 'monthly',
      priority: 0.7,
      alternates: {
        languages: {
          pl: `${baseUrl}/blog/jak-zbudowac-api`,
          en: `${baseUrl}/en/blog/how-to-build-an-api`,
        },
      },
    },
  ]
}

Google wymaga, żeby każda wersja językowa wskazywała na pozostałe wersje w alternates (symetrycznie), jednostronne deklaracje hreflang są ignorowane.

Wiele sitemap dla dużych serwisów

Sitemap ma limit 50 000 URL-ów lub 50 MB (nieskompresowany), dla dużych serwisów — generuj wiele sitemaps z indeksem:

Code
// app/sitemap.ts
import type { MetadataRoute } from 'next'
import { db } from '@/lib/db'
 
export async function generateSitemaps() {
  const totalPosts = await db.post.count({ where: { published: true } })
  const sitemapsNeeded = Math.ceil(totalPosts / 50000)
 
  return Array.from({ length: sitemapsNeeded }, (_, i) => ({ id: i }))
}
 
export default async function sitemap({
  id,
}: {
  id: number
}): Promise<MetadataRoute.Sitemap> {
  const baseUrl = 'https://example.com'
  const limit = 50000
  const offset = id * limit
 
  const posts = await db.post.findMany({
    where: { published: true },
    select: { slug: true, updatedAt: true },
    skip: offset,
    take: limit,
  })
 
  return posts.map((post) => ({
    url: `${baseUrl}/blog/${post.slug}`,
    lastModified: post.updatedAt,
  }))
}

Next.js automatycznie generuje indeks sitemaps: /sitemap.xml → zawiera linki do /sitemap/0.xml, /sitemap/1.xml itd.

Natywny robots.txt w Next.js App Router

Code
// app/robots.ts
import type { MetadataRoute } from 'next'
 
export default function robots(): MetadataRoute.Robots {
  const baseUrl = 'https://example.com'
 
  return {
    rules: [
      {
        userAgent: '*',
        allow: '/',
        disallow: ['/api/', '/admin/', '/dashboard/'],
      },
      {
        userAgent: 'GPTBot',
        disallow: '/', // Prośba o brak crawlowania dla bota respektującego robots.txt
      },
    ],
    sitemap: `${baseUrl}/sitemap.xml`,
  }
}

Wynikowy /robots.txt:

Code
User-agent: *
Allow: /
Disallow: /api/
Disallow: /admin/
Disallow: /dashboard/

User-agent: GPTBot
Disallow: /

Sitemap: https://example.com/sitemap.xml

next-sitemap: kiedy natywne API Next.js nie wystarczy?

Biblioteka next-sitemap oferuje dodatkowe funkcje, czyli automatyczne wykrywanie statycznie wygenerowanych stron po buildzie, wielojęzyczne sitemaps z hreflang, server-side sitemap regeneration i konfigurację per-ścieżka.

Code
npm install next-sitemap
Code
// next-sitemap.config.js
module.exports = {
  siteUrl: 'https://example.com',
  generateRobotsTxt: true,
  changefreq: 'weekly',
  priority: 0.7,
  sitemapSize: 5000,
  exclude: ['/api/*', '/admin/*', '/dashboard/*'],
  robotsTxtOptions: {
    additionalSitemaps: ['https://example.com/server-sitemap.xml'],
    policies: [
      { userAgent: '*', allow: '/' },
      { userAgent: 'GPTBot', disallow: '/' },
    ],
  },
  transform: async (config, path) => {
    // Custom priorytety per ścieżka
    if (path === '/') return { loc: path, priority: 1.0, changefreq: 'daily' }
    if (path.startsWith('/blog/'))
      return { loc: path, priority: 0.7, changefreq: 'monthly' }
    return {
      loc: path,
      priority: config.priority,
      changefreq: config.changefreq,
    }
  },
}
Code
// package.json
{
  "scripts": {
    "postbuild": "next-sitemap"
  }
}

Natywne API Next.js vs next-sitemap

FunkcjaNatywne APInext-sitemap
Automatyczne wykrywanie stronNieTak (po buildzie)
Dynamiczne strony z DBTak (ręczne)Tak (server sitemap)
Hreflang / alternatesTak (pole alternates)Tak (automatyczne)
Wielki serwis (> 50K stron)Tak (generateSitemaps)Tak (sitemapSize)
Konfiguracja per-ścieżkaRęczna (w kodzie)transform function
Dodatkowe zależnościZero1 pakiet
Koszt utrzymaniaMinimalny (część frameworka)Wyższy (zależność do aktualizacji)

Dla większości projektów, zwłaszcza tych z przewidywalną strukturą, natywne API Next.js jest w zupełności wystarczające, prostsze i nie wprowadza dodatkowych zależności. Sięgnij po next-sitemap tylko w wyjątkowych sytuacjach, gdy masz bardzo duży, nietypowy serwis, gdzie automatyczne wykrywanie stron i zaawansowane transformacje oszczędzą Ci mnóstwa pracy manualnej.

Weryfikacja sitemap i robots.txt w Google Search Console

Po wdrożeniu sitemap i robots.txt:

  1. Otwórz Google Search Console → Sitemaps
  2. Dodaj URL sitemap: https://example.com/sitemap.xml
  3. Sprawdź status — Google pokaże liczbę wykrytych i zaindeksowanych stron
  4. Monitoruj zakładkę „Strony" — szukaj błędów indeksowania
Audyt techniczny i optymalizacja pod kątem SEO i GEO.
Audyt techniczny SEO

Często zadawane pytania

Czy sitemap gwarantuje indeksację strony?

Nie. Sitemap pomaga wyszukiwarkom odkrywać adresy i daty zmian, ale nie gwarantuje indeksacji ani wysokiej pozycji. Strona nadal musi być dostępna i wartościowa.

Czy robots.txt blokuje indeksację?

Robots.txt kontroluje crawling, a nie zawsze samą indeksację. Jeśli chcesz usunąć stronę z indeksu, zwykle potrzebujesz noindex lub usunięcia adresu, a nie tylko blokady w robots.txt.

Kiedy użyć next-sitemap zamiast natywnego app/sitemap.ts?

Natywne API App Routera wystarcza w wielu projektach. next-sitemap może być wygodny przy bardziej rozbudowanych konfiguracjach, starszych strukturach lub gotowym workflow opartym o ten pakiet.

Czy sitemap jest wymagana do indeksowania?

Nie, Google może indeksować strony bez sitemap (przez linki wewnętrzne i zewnętrzne), ale sitemap przyspiesza odkrywanie nowych stron i informuje o zmianach. Jest to szczególnie w dużych serwisach lub przy braku zewnętrznych linków.

Jak często Google czyta sitemap?

Google sprawdza sitemap okresowo i nie ma jakiejś z góry ustalonej częstotliwości. Możesz wymusić ponowne sprawdzenie w Search Console (przycisk „Prześlij ponownie") i czekać na wynik.

Czy `changefreq` i `priority` wpływają na pozycje w Google?

Nie, Google wskazuje, że oba pola traktuje tylko jako wskazówki i w praktyce je ignoruje. Warto je wypełniać dla innych wyszukiwarek (Bing, Yandex), które mogą je respektować, ale nie mają żadnego wpływu na wyniki w Google - oczywiście, jeśli przyjmujemy, że wszystko co twierdzi Google odpowiada rzeczywistości.

Czy blokowanie botów AI w robots.txt jest skuteczne?

Działa dla botów, które respektują plik robots.txt. Zarówno GPTBot (OpenAI), jak i Google-Extended (Gemini) deklarują, że to robią. Jednak robots.txt to tylko prośba, a nie techniczna zapora — złośliwy lub źle skonfigurowany crawler może zignorować te dyrektywy. Jest to jednak obecnie standardowa metoda komunikowania swoich preferencji botom AI.

O autorze

Maciej Sala

Maciej Sala — Product Manager i Frontend Developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwijam własne projekty.

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną

Biblioteka wiedzy na temat Next.js

Czytaj dalej

Zobacz więcej wpisów
Dynamiczna sitemap.xml w Astro z API CMS-a

Masz Astro, masz też headless CMS Payload albo Sanity i masz problem: po każdej publikacji albo ręcznie dopisujesz URL-e do sitemapy, albo liczysz, że Googlebot sam wpadnie na nowy artykuł. Pierwsze nie skaluje się powyżej kilkunastu podstron. Drugie zostawia odkrywanie URL-i przypadkowi. Pokażę architekturę i kod w dwóch wariantach: prostszym @astrojs/sitemap z serialize oraz z pełną kontrolą własny endpoint XML . Obie wersje generują statyczny plik na buildzie, więc serwer nie odpytuje CMS-a przy każdym wejściu Googlebota.

Maciej Sala

Maciej Sala

Founder StriveLab

Google Search Console + Next.js — indeksacja, błędy, performance i co z nimi robić

Trudno wyobrazić sobie współczesną analitykę bez Google Search Console GSC , czyli podstawowego narzędzia pokazującego dane bezpośrednio z Google Search. PageSpeed Insights mierzy wydajność, Ahrefs śledzi backlinki, ale GSC pokazuje całą paletę danych ale też problemów/błędów dotyczących strony internetowej. Przykładowo, które strony są zaindeksowane, jakie błędy crawlowania występują, na jakie frazy rankujesz i jakie wyniki Core Web Vitals Dane Core Web Vitals w GSC pochodzą z Chrome UX Report, czyli realnych wizyt użytkowników, a nie z pojedynczego testu laboratoryjnego Lighthouse. mają istniejący użytkownicy.

Maciej Sala

Maciej Sala

Founder StriveLab

Hreflang i link kanoniczny w Next.js — SEO wielojęzycznych stron bez duplikacji

Hreflang i link kanoniczny rozwiązują dwa różne problemy wielojęzycznego SEO: właściwy język wyniku i właściwy adres do indeksowania. W Next.js App Router oba mechanizmy da się generować spójnie przez Metadata API , bez ręcznego utrzymywania tagów w każdej podstronie.

Maciej Sala

Maciej Sala

Founder StriveLab