Joost de Valk opublikował 27 września 2026 r. tekst o tym, że prawie nikt nie robi sitemap XML poprawnie. Przejrzał wtyczką Sitemap Inspector sześć serwisów, od Adobe po GOV.UK, i w każdym znalazł to samo: adresy z noindex, przekierowania, 404, kanoniczne wskazujące gdzie indziej, jedną datę na setki wpisów. Jego teza brzmi: „A sitemap should list the URLs a site wants indexed, and nothing else.”
Zgadzamy się z tezą i z listą. Sprawdziliśmy nią własny serwis. Między sierpniem a październikiem 2026 r. znaleźliśmy w generatorze sitemap wppoland.com pięć błędów, każdy zmierzony na produkcji. Trzy z nich lista Joosta by złapała. Dwóch nie złapie żaden inspektor, który czyta wpisy sitemapy, bo polegały na tym, czego w niej nie było. Ten tekst jest o obu rodzajach.
Kontekst techniczny: wppoland.com to serwis Astro w sześciu językach, około 6700 adresów w sitemapach. Sitemapy generuje własna integracja astro-sitemaps.mjs, nie wtyczka. To ważne dla wniosków: każdy z błędów poniżej siedział w kodzie, który czyta dane i buduje listę, a nie w samej liście.
Błąd 1: data builda jako lastmod
24 sierpnia 2026 r. pięć różnych sitemap miało dokładnie jedną wartość lastmod na wszystkie wpisy, tę samą, dzisiejszą. Integracja liczyła new Date() raz i stemplowała nią 3079 adresów. Każde wdrożenie ogłaszało Google, że zmieniło się wszystko.
Google opisuje warunek wprost: używa <lastmod>, jeśli wartość jest „consistently and verifiably” zgodna z ostatnią zmianą strony (Google Search Central, strona o budowie sitemap, aktualizacja z 8 lipca 2026 r.). Data, która skacze przy każdym buildzie, tego warunku nie spełnia, więc Google przestaje jej ufać dla całej domeny, także tam, gdzie byłaby prawdziwa. W próbkach z tamtego okresu ostatni crawl niektórych stron sięgał od trzech tygodni do trzech miesięcy wstecz.
Naprawa podstawiła updatedDate z frontmattera, w razie braku pubDate. Efekt na artefakcie: polska sitemapa bloga przeszła z 1 na 98 różnych dat.
Przy tej naprawie łatwo o błąd drugiego rzędu. Kuszące było podstawienie pola lastVerified dla stron miast, bo ma je każdy z 4733 plików. Tyle że 4697 z 4733 plików ma w nim tę samą wartość. To zbiorczy stempel, nie sygnał, i przeniósłby ten sam defekt z „dzisiaj” na inną stałą datę. Zanim jakiekolwiek pole daty trafi do lastmod, trzeba policzyć, ile ma różnych wartości.
Naprawa z sierpnia objęła blog, portfolio i strony z kolekcji treści. Nie objęła ręcznie pisanych tras .astro. Artykuł Joosta skłonił nas do ponownego sprawdzenia: 29 września w /pl/sitemap-pages.xml 62 z 113 adresów nadal miało jako lastmod datę builda, bo nie istnieje dla nich data w treści, a kod spadał wtedy na today. Poprawka (commit af97c7a887) bierze dla takich tras datę ostatniego commita pliku źródłowego z jednego wywołania git log na build, a gdy i tej nie ma, pomija lastmod. Wspólny szablon [lang]/[slug].astro świadomie nie liczy się jako źródło, bo jego data nic nie mówi o żadnej konkretnej stronie. Ten sam plik po poprawce: 87 z 113 adresów ma lastmod, 42 różne daty, najczęstsza przypada na 7 adresów, a 26 adresów nie ma daty wcale. W polskiej sitemapie stron miast przed poprawką 684 z 692 wpisów miało datę builda, po niej 8 wpisów ma prawdziwą datę: szablon miast nie ma daty, której dałoby się uczciwie użyć, więc reszta daty nie deklaruje. Poprawka jest na produkcji od 29 września 2026 r., a żywy plik pokazuje te same liczby.
Błąd 2: filtr noindex pilnował jednego pola
Po obniżeniu 723 stron miast do noindex Search Console dalej zgłaszał je jako odkryte. Same strony miały poprawny noindex. Wracały do Google inną drogą: sitemap-locations.xml wymieniała je jako alternatywy językowe (xhtml:link, także x-default) stron, które zostały w indeksie.
Filtr noindex w integracji sprawdzał tylko <loc>. Alternatywy pochodzą z nagłówka strony, który wymienia wszystkie wersje językowe, więc filtr ich nie widział. Poprawka zostawia tylko te alternatywy, których adres sam jest <loc> w sitemapie. Po niej build dawał 6407 adresów i 0 błędnych alternatyw.
Wniosek wykracza poza sitemapy: filtr na jednym polu wpisu nie obejmuje pozostałych pól tego wpisu, które niosą adres. W sitemapie takich miejsc jest kilka: loc, alternatywy, x-default, image:loc.
Błąd 3: 500 działających stron poza sitemapą
21 września 2026 r. integracja zaczęła pomijać każdy adres pasujący do prefiksów, które middleware w functions/_middleware.ts przekierowuje. Tylko że middleware przekierowuje je wyłącznie wtedy, gdy zasób zwraca 404, a każdy kandydat do sitemapy jest zbudowaną stroną, więc nigdy nie zwraca 404. Filtr skopiował regułę bez jej warunku.
Wynik: 500 działających stron z index, follow wypadło z sitemapy, łącznie z celem jednego z przekierowań (/pl/audyt-bezpieczenstwa-wordpress/ pasował do prefiksu audyt-bezpieczenstwa-). Poprawka weszła 26 września, build z sitemapą dał +500 adresów i 0 usuniętych.
Tego błędu nie złapie żadna kontrola wpisów. Każdy z pozostałych adresów był poprawny: renderował się, nie miał noindex, nie przekierowywał. Mniej adresów w sitemapie wygląda jak porządki, a nie jak defekt. Wykryć to można tylko porównaniem zbioru adresów przed zmianą i po niej.
Błąd 4: >- jako adres obrazka
Integracja wyciągała heroImage z frontmattera wyrażeniem regularnym, linia po linii. Przy bloku składanym YAML (heroImage: >-) wartość stoi w następnej linii, więc regex łapał dosłowne >-. Sitemapa dostawała wpisy <image:loc>https://wppoland.com/>-</image:loc>.
17 września 2026 r. na produkcji było 31 takich wpisów w sześciu językach. Search Console pokazywał pięć błędów na pl/sitemap-blog.xml, a sam XML był poprawnie zbudowany i każdy <loc> był w porządku. YAML w plikach też był poprawny. Zepsuty był czytający.
Poprawka parsuje frontmatter parserem YAML, a funkcję wyciągającą metadane wyeksportowaliśmy z integracji, żeby test mógł jej dosięgnąć bez pełnego builda. Wcześniej siedziała w środku 900-liniowego pliku i żaden test nie miał do niej dostępu.
Błąd 5: IndexNow wysyłał adresy, których nie ma
Skrypt do IndexNow budował adresy z nazw plików w src/content/. 17 września 2026 r. generował 2328 adresów, z których 0 występowało w sitemapie. Wpisy bloga dostawały segment /blog/, którego w adresach nie ma, a strony zachowywały sufiks języka z nazwy pliku (/de/about.de/). Oba warianty zwracały 301. Do tego wzorzec plików łapał tylko .md, więc 100 filarów usługowych w .mdx nie było wysyłanych nigdy.
Poprawka jest jednym zdaniem: źródłem adresów do IndexNow jest sitemap-index.xml. Ten zbiór jest już sprawdzony: strony renderują się i nie mają noindex. Heurystyka z nazw plików zniknęła razem z trzema klasami błędów.
Błąd bez błędu: Google nie pobrał sitemapy
Ten przypadek nie był defektem generatora, ale należy do tej samej rodziny. Na początku września 2026 r. po odblokowaniu stron miast sześć sitemap lokalizacji liczyło 4071 adresów. Wszystko po naszej stronie było poprawne: próbki zwracały 200 i index, follow, żaden adres z sitemapy nie był noindex ani przekierowaniem.
Search Console ostatni raz pobrał te sitemapy 1 września o 10:08, gdy miały 2037 adresów, i przez pięć dni ich nie odświeżył. Sitemapy bloga czytał codziennie. Wszystko dodane po tej godzinie dla Google nie istniało. Ponowne przesłanie przez API zostało pobrane tego samego dnia.
W kolejnym tygodniu liczba stron miast z wyświetleniami wzrosła z 223 do 343, a ich wyświetlenia z 1570 do 4382. Te 343 strony to 8,4 procent z 4071 i w siedem dni dały 2 kliknięcia. Odblokowane zostało odkrycie, nie ruch, i to są dwie różne liczby.
Czego inspektor wpisów nie zobaczy
| Błąd | Jak wyglądał w sitemapie | Czy wykryje go kontrola wpisów | Co go wykryło |
|---|---|---|---|
| Data builda jako lastmod | wszystkie wpisy z jedną datą | tak, jako powtarzający się lastmod | liczba różnych dat w pliku |
| Alternatywy obok noindex | poprawne <loc>, błędne xhtml:link | częściowo, jeśli sprawdza alternatywy | raport Search Console o noindex |
| 500 stron poza sitemapą | każdy obecny wpis poprawny | nie | porównanie zbioru adresów przed i po |
>- jako obrazek | 31 błędnych image:loc | tak, jeśli sprawdza obrazki | błędy sitemap w Search Console |
| IndexNow spoza sitemapy | sitemapa poprawna | nie, błąd poza sitemapą | przecięcie listy wysyłki z sitemapą |
| Sitemapa niepobrana | plik poprawny i aktualny | nie | data pobrania w Search Console |
Wtyczka taka jak ta, której użył Joost, odpowiada na pytanie „czy to, co jest w sitemapie, jest poprawne”. To dobre pytanie i większość serwisów, jak pokazał, na nie nie odpowiada. Ale trzy z sześciu naszych przypadków dotyczyły czego innego: czego w sitemapie nie ma, co z niej korzysta i którą wersję widzi Google.
Lista kontrolna dla generatora sitemap
Kontrola wpisów jest warunkiem koniecznym, nie wystarczającym. Te pięć punktów sprawdza generator, a nie sam plik:
- Policz różne wartości
lastmodw każdym pliku. Jedna wartość na setki wpisów oznacza stempel, nie datę. To samo przed podstawieniem nowego pola daty: jeśli prawie wszystkie pliki mają w nim tę samą wartość, pole nie nadaje się nalastmod. Braklastmodjest lepszy niż nieprawdziwy. - Porównuj zbiór adresów przed zmianą generatora i po niej. Liczba dodanych i usuniętych adresów, z listą. Spadek liczby adresów wymaga wyjaśnienia tak samo jak wzrost.
- Każde pole niosące adres podlega tym samym filtrom.
loc, alternatywy językowe,x-default,image:loc. Filtr na jednym z nich nie chroni pozostałych. - Reguła skopiowana z innego komponentu przychodzi razem ze swoim warunkiem. Przekierowanie „gdy 404” to nie to samo co przekierowanie zawsze.
- Sitemapa jest jedynym źródłem adresów dla wszystkiego, co wysyła adresy dalej: IndexNow, Indexing API, listy do ręcznego zgłoszenia. I raz na tydzień: data ostatniego pobrania w Search Console i liczba adresów, którą tam widać, obok liczby
<loc>w żywym pliku.
Dwie rzeczy z dokumentacji Google, które warto mieć pod ręką: jeden plik sitemapy mieści najwyżej 50 000 adresów lub 50 MB bez kompresji, a wartości <priority> i <changefreq> są ignorowane. Nie szkodzą, ale nie warto nad nimi pracować.
Jeśli serwis działa jako headless WordPress, dochodzi pytanie, kto w ogóle generuje sitemapę, front czy WordPress. Opisaliśmy to osobno w tekście o sitemapie i kanonicznym adresie w headless WordPress.







