Szybka odpowiedź: jak wygenerować sitemap.xml w Astro z CMS?
Najkrótsza odpowiedź brzmi: pobierz z CMS-a wszystkie opublikowane i indeksowalne rekordy, wygeneruj odpowiadające im strony przez getStaticPaths, a @astrojs/sitemap pozwól zbudować XML. Pole updatedAt przekaż do <lastmod>, publikację w CMS-ie połącz webhookiem z nowym buildem, a wynik sprawdź przed wdrożeniem.
W tym kontekście słowo „dynamiczna" opisuje przede wszystkim dynamiczne źródło danych, nie sposób serwowania pliku. Wynikiem może być całkowicie statyczny sitemap-index.xml. To zwykle najlepsze połączenie świeżych danych z CMS-a i odporności pliku dostarczanego z CDN.
Jeśli nowa treść nie jest szybko odkrywana, tracisz jej najbardziej aktualny okres, co przy newsach, landingach kampanii czy ofertach czasowych może oznaczać utracony ruch. Sitemapa nie gwarantuje indeksacji, ale pomaga crawlerowi odkryć kanoniczne adresy i rozpoznać istotne aktualizacje. Jej automatyzacja ma sens od chwili, gdy ręczne utrzymywanie listy URL-i staje się podatne na pomyłki. Nie istnieje przy tym magiczny próg liczby podstron, od którego sitemapa staje się obowiązkowa.
Architektura: build-time jako domyślny wybór
Przepływ danych wygląda tak:
W większości projektów budujemy sitemapę w czasie build, a nie w czasie żądania. To rozróżnienie ma realne konsekwencje dla wydajności i odporności:
- Generowanie statyczne (, domyślne w Astro): sitemapa powstaje raz podczas builda. To plik XML serwowany z . nie dotyka wtedy bazy ani API CMS-a. Odpowiedź jest szybka i nie zależy od bieżącej dostępności zaplecza.
- Generowanie na żądanie (): endpoint może pobierać aktualne dane bez nowego deployu, ale niezbuforowana wersja zwiększa obciążenie CMS-a i ryzyko timeoutu. Jeśli ten wariant jest konieczny, generuj migawkę w tle albo stosuj długi cache i mechanizm
stale-if-error.
Build-time jest właściwym wyborem, jeśli publikacja, aktualizacja albo usunięcie treści uruchamia nowy deploy. Sam CMS tego nie zapewnia. Potrzebujesz webhooka do platformy hostingowej lub innego mechanizmu wyzwalającego build.
| Sytuacja | Zalecany wariant | Dlaczego |
|---|---|---|
| Astro SSG, treść publikuje się przez webhook | @astrojs/sitemap + getStaticPaths | Najmniej własnego kodu, automatyczne wykrywanie zbudowanych tras |
| Projekt SSR, ale mapa może odświeżać się przy deployu | Statyczny endpoint z export const prerender = true | Strony mogą być SSR, a XML pozostaje szybki i niezależny od CMS-a |
| Trasy SSR nie są znane podczas builda | customPages pobrane z CMS-a albo własny endpoint | Integracja nie potrafi automatycznie rozwinąć nieprerenderowanych tras dynamicznych |
| Bardzo częste zmiany bez deployu | Buforowany endpoint lub okresowo generowany plik | Aktualność bez odpytywania CMS-a przy każdym żądaniu |
Jakie URL-e powinny trafić do sitemap.xml w Astro
Sitemapa nie jest listą wszystkich adresów, które technicznie istnieją w aplikacji. To lista URL-i, które chcesz widzieć w wynikach wyszukiwania. Dodawaj więc tylko adresy, które:
- Zwracają HTTP 200.
- Nie używają .
- Są kanoniczną wersją danej treści.
- Nie są przekierowaniem, fallbackiem ani stroną błędu.
- Mają realną treść i nie są pustym szablonem z CMS-a.
- Nie są wariantem śledzącym lub sortującym, takim jak
?utm_source=czy?sort=.
W praktyce filtruj dane już na etapie pobierania z CMS-a: status === 'published', slug istnieje, canonicalUrl nie wskazuje gdzie indziej, a rekord nie jest wersją roboczą. Jeżeli strona ma robots: noindex, nie powinna pojawić się w sitemapie. Jeżeli URL przekierowuje na inny adres, w sitemapie powinien być adres docelowy. Paginacji nie wykluczaj automatycznie. Adresy typu /blog/strona/2/ mogą być poprawnymi, samodzielnymi stronami, jeśli są kanoniczne i pomagają crawlerowi dotrzeć do starszych treści.
Implementacja sitemap.xml w Astro krok po kroku
Krok 1: Instalacja @astrojs/sitemap
Oficjalna integracja Astro. Instalacja jednym poleceniem:
Wymaga ustawionego site w konfiguracji. Bez tego integracja nie zbuduje absolutnych URL-i:
Najważniejszym mechanizmem jest @astrojs/sitemap, który wykrywa strony zbudowane podczas builda, włącznie z tymi wygenerowanymi przez getStaticPaths. Nie musisz ręcznie wpisywać ich ścieżek. Jest jeden ważny wyjątek: integracja nie generuje wpisów dla wariantów tras dynamicznych, które istnieją wyłącznie w trybie SSR i nie mają listy ścieżek w czasie builda.
Jeśli łączysz statyczne strony z dynamicznymi trasami SSR, możesz podać te drugie jako customPages:
Lista nadal musi pochodzić z pełnego, przefiltrowanego odczytu CMS-a. customPages rozwiązuje problem odkrywania tras przez integrację, ale nie zastępuje walidacji danych.
Krok 2: pobranie wszystkich rekordów, a nie tylko pierwszej strony API
To miejsce, w którym najłatwiej stworzyć poprawny XML z niepełną zawartością. Payload ma paginowane zapytania, a odpowiedź zawiera między innymi totalPages, hasNextPage i nextPage. Nie polegaj na limit=10000: serwer może ograniczyć maksymalny limit, zapytanie może stać się ciężkie, a wraz ze wzrostem bazy założenie w końcu przestanie działać.
Poniżej znajduje się uproszczony loader dla Payload CMS. Filtruje rekordy po stronie API, pobiera je partiami i zatrzymuje build, gdy wykryje brak slugu, nieprawidłową datę albo duplikat:
W Payload CMS z włączonymi wersjami roboczymi pole publikacji nazywa się _status. Jeśli w Twojej kolekcji istnieje własne pole status, dostosuj filtr do schematu. W produkcji pobieraj tylko pola potrzebne do danego etapu. Lista do sitemapy zwykle potrzebuje slug, updatedAt, statusu indeksacji i ewentualnego canonicalUrl. Pełną treść artykułu możesz pobrać osobno przy renderowaniu strony. W Sanity odpowiednikiem dla dużych zbiorów jest paginacja kursorem po stabilnym i jednoznacznym polu, na przykład _id, zamiast coraz większych zakresów tablicy.
Krok 3: generowanie podstron przez getStaticPaths
To jest ważny szczegół obsługi tras typu /blog/[slug]. W pliku trasy dynamicznej pobierasz dane z API i zwracasz listę ścieżek. Każda z nich stanie się statyczną stroną i automatycznie wpadnie do sitemapy.
W przykładzie funkcję
getAllPublishedPostswarto umieścić we wspólnym module. Jeżeli karta artykułu potrzebuje całej treści, loader powinien zwracać ją wpropsalbo strona może wykonać osobne zapytanie po slugu.
Po tym kroku @astrojs/sitemap zna już wszystkie adresy /blog/<slug>/. Brakuje mu tylko jednej rzeczy: daty ostatniej modyfikacji.
Krok 4: mapowanie updatedAt na <lastmod> przez serialize
Integracja nie analizuje kodu źródłowego strony, więc sama nie wie, kiedy dany artykuł był aktualizowany. Tę informację wstrzykujemy w hooku serialize, który jest wołany dla każdego wpisu tuż przed zapisem na dysk. Najczystsze podejście: raz pobrać dane z CMS, przerwać build przy błędzie i zbudować mapę ścieżka → updatedAt.
To wszystko, czego potrzebuje większość projektów. Jeden build i masz sitemap-index.xml z poprawnymi datami <lastmod> dla każdego opublikowanego artykułu. Pamiętaj, że konfiguracja sitemapy i getStaticPaths mogą pobrać dane osobno. Ogranicz zakres pól, użyj cache klienta CMS albo przygotuj jedną migawkę danych dla builda, jeśli podwójny odczyt jest kosztowny.
Krok 5: webhook z CMS-a uruchamiający build
Statyczna sitemapa jest aktualna tylko tak długo, jak aktualny jest deploy. Skonfiguruj webhook dla zdarzeń publikacji, aktualizacji, usunięcia i cofnięcia publikacji. Powinien wywoływać build hook hostingu. Dzięki temu ta sama operacja aktualizuje stronę, linkowanie wewnętrzne i sitemapę.
Zabezpiecz webhook sekretem i ogranicz liczbę buildów. Gdy redaktor zapisuje dokument kilka razy w ciągu minuty, debounce lub kolejka mogą połączyć serię zdarzeń w jedno wdrożenie. Po deployu wykonaj test dymny: sprawdź, czy nowy URL występuje w sitemapie i zwraca 200.
Wariant „gotowiec": własny endpoint z pełną kontrolą
Czasami potrzebujesz pełnej kontroli nad XML-em: własnego podziału na pliki, osobnych reguł albo danych niezależnych od automatycznego wykrywania stron. W takiej sytuacji utwórz statyczny endpoint. Poniższy przykład korzysta z paginowanego loadera z wcześniejszej sekcji:
W domyślnym trybie statycznym Astro ten endpoint jest renderowany raz, podczas builda. Efekt jest taki sam jak przy @astrojs/sitemap, czyli zwykły plik na CDN. export const prerender = true ma znaczenie w projekcie z renderowaniem serwerowym, ponieważ wymusza statyczność tego konkretnego pliku.
Ten przykład tworzy wyłącznie blog-sitemap.xml. Nadal potrzebujesz mapy dla pozostałych sekcji i indeksu wskazującego wszystkie pliki albo musisz zgłosić poszczególne mapy osobno. Jeśli równolegle używasz @astrojs/sitemap, wyklucz własne endpointy XML z automatycznie generowanej mapy, aby sitemapa nie wskazywała samej siebie:
Własny pojedynczy endpoint nadaje się tylko wtedy, gdy mieści się w limicie 50 000 URL-i i 50 MB bez kompresji. Powyżej tego progu generator musi utworzyć kilka plików <urlset> oraz indeks <sitemapindex>. Integracja robi ten podział za Ciebie, dlatego własny XML warto wybierać tylko wtedy, gdy dodatkowa kontrola rzeczywiście uzasadnia kod i testy.
Sitemap.xml a : co naprawdę czyta Google?
<lastmod> musi oznaczać istotną zmianę
<lastmod> to opcjonalny tag, który Google może wziąć pod uwagę, ale tylko wtedy, gdy jest spójny i możliwy do zweryfikowania. Poprawna data aktualizacji mówi: „tu zmieniła się istotna treść, warto wrócić". Nie powinna oznaczać każdego builda, deployu, zmiany stopki albo aktualizacji informacji o prawach autorskich.
Jest jednak haczyk, o którym mało kto pisze: Google ufa lastmod tylko, jeśli jest wiarygodny. Jeśli na każdym buildzie ustawisz lastmod na „teraz" dla wszystkich stron, Google szybko zauważy, że daty nie odpowiadają zmianom treści, i zacznie je ignorować. Częstym błędem jest użycie new Date() zamiast daty z CMS-a. Dlatego w kodzie wyżej lastmod pochodzi wprost z updatedAt, a nie z czasu builda. To różnica między użytecznym sygnałem a szumem.
priority i changefreq nie są strategią indeksacji
Możesz ustawić priority i changefreq dla porządku albo dla narzędzi, które je analizują, ale Google oficjalnie ignoruje oba tagi. Nie buduj strategii indeksacji wokół wartości 0.8 czy weekly. Większe znaczenie mają: kanoniczny URL, poprawny status HTTP, brak noindex, linkowanie wewnętrzne i wiarygodny lastmod.
Sitemap Index: co zrobić przy tysiącach stron?
Pojedynczy plik sitemapy ma limity: maksymalnie 50 000 URL-i i 50 MB bez kompresji. @astrojs/sitemap domyślnie dzieli wynik przy entryLimit: 45000, zostawiając bezpieczny margines. Przy dużych katalogach produktów czy rozbudowanym blogu warto dodatkowo dzielić sitemapę na mniejsze pliki tematyczne. Nie tylko z konieczności technicznej, ale także dlatego, że łatwiej diagnozuje się indeksację w , gdy każdy typ treści ma osobny plik.
W @astrojs/sitemap służy do tego opcja chunks:
Powstaną osobne pliki (sitemap-blog-0.xml, sitemap-produkty-0.xml oraz domyślny zbiór dla reszty), wszystkie spięte w sitemap-index.xml. Gdy liczba indeksowanych wpisów blogowych spadnie, szybciej rozpoznasz w GSC, której sekcji dotyczy problem.
Opcja chunks jest dostępna w @astrojs/sitemap od wersji 3.7.0. Jeśli korzystasz ze starszej wersji, zaktualizuj integrację albo zastosuj kilka własnych endpointów i osobny indeks sitemap.
Wielojęzyczne adresy i hreflang
Jeżeli serwis ma równoważne wersje językowe, alternatywy możesz opisać w sitemapie. Integracja Astro obsługuje konfigurację i18n, ale adresy muszą odpowiadać realnym, kanonicznym stronom. Nie dodawaj automatycznie tłumaczenia, które nie istnieje albo przekierowuje na język domyślny. To samo powiązanie powinno być wzajemne: polska wersja wskazuje angielską, a angielska polską.
Monitoring i utrzymanie sitemap.xml w Astro
Weryfikacja w Google Search Console
Po wdrożeniu zgłoś sitemapę w GSC. Wejdź w raport Mapy witryn → dodaj adres https://example.com/sitemap-index.xml. Po przetworzeniu zobaczysz status (powinien być „Sukces"), datę ostatniego odczytu i liczbę wykrytych adresów. Jeśli liczba „wykrytych" mocno odbiega od realnej liczby treści, masz wtedy wyraźny sygnał, że coś jest nie tak. Rozbicie na pliki tematyczne (sekcja wyżej) sprawia, że od razu wiadomo gdzie.
Dodaj też sitemapę do robots.txt, żeby crawler znalazł ją bez ręcznego zgłoszenia w panelu:
Zgłoszenie sitemapy pomaga wyszukiwarkom w odkrywaniu nowych adresów, ale nie gwarantuje natychmiastowego crawlowania ani indeksacji i dlatego każda nowa podstrona wciąż potrzebuje solidnych linków wewnętrznych. Pamiętaj, że mapa witryny jest cennym uzupełnieniem architektury informacji, ale jej nie zastępuje.
Kontrola jakości po każdym buildzie
Sam status 200 dla pliku XML to za mało. Do CI warto dodać test, który po astro build sprawdzi:
- Czy wszystkie mapy są poprawnym XML-em i nie przekraczają limitów.
- Czy każdy
<loc>jest absolutnym adresem HTTPS we właściwej domenie. - Czy adresy są unikalne i nie zawierają parametrów śledzących.
- Czy liczba URL-i nie spadła nagle względem poprzedniego wdrożenia.
- Czy próbka stron zwraca 200, nie ma
noindexi wskazuje samą siebie jako adres kanoniczny. - Czy daty
<lastmod>są prawidłowe i nie leżą przypadkowo w przyszłości.
Próg liczby adresów powinien uwzględniać naturalną rotację i usuwanie treści. W przypadku serwisu posiadającego 12 000 produktów znacznie lepiej sprawdzi się alarm ustawiony na spadek liczby URL-i o 20 procent. Poza tym warto również monitorować i zapisywać historyczne statystyki liczby adresów dla każdego pliku tematycznego z osobna – taka prosta seria czasowa pozwala błyskawicznie wychwycić nagłe błędy w filtrowaniu, paginacji lub działaniu webhooków.
Ostrzeżenie: co, jeśli API CMS-a padnie podczas builda?
To realne ryzyko, które trzeba obsłużyć w sposób przemyślany. Jeśli fetch do CMS-a zwróci błąd albo niepełną listę w trakcie builda, możliwe są dwa scenariusze:
- build kończy się błędem i ostatnie poprawne wdrożenie pozostaje aktywne,
- build przechodzi z niepełną listą i może wdrożyć pustą sitemapę oraz brak statycznych podstron bloga.
Sama pusta sitemapa nie wydaje Google polecenia usunięcia stron z indeksu. Zagrożeniem jest wdrożenie stron zwracających 404 albo długotrwała utrata sygnałów odkrywania. Dlatego rekomenduję podejście fail-fast dla danych krytycznych. Niech build zatrzyma się z czytelnym błędem, zamiast po cichu zastępować poprawny serwis niepełną wersją.
Wariant pośredni dla dużych serwisów to wczytanie ostatniej poprawnej migawki danych z kontrolowanego magazynu, aby chwilowa awaria CMS-a nie blokowała wydania. Każda migawka musi być odpowiednio wersjonowana, posiadać określoną datę ważności oraz przechodzić te same testy co standardowe dane. Stosowanie cichego fallbacku do przypadkowego, lokalnego pliku to prosta droga do tego, by chwilowy problem sieciowy przerodził się w wielotygodniową, niezauważoną nieaktualność serwisu.
W sytuacji, kiedy budujesz rozwiązanie dla biznesu i zależy Ci na solidnej architekturze, nie tylko stronie, ale całym ekosystemie treści na Astro i headless CMS, napisz do mnie w sprawie wdrożenia. A jeśli serwis już działa, ale indeksacja kuleje, zacznij od audytu technicznego SEO.


