SEO w headless WordPress: siedem rzeczy, które psuje większość migracji
Headless WordPress sprzedaje się na Core Web Vitals, wielokrotnym użyciu treści i szybkości pracy redakcji. Jeśli nikt nie pilnuje, po drodze grzebie siedem konkretnych sygnałów SEO. Przeprowadziliśmy dość takich migracji, żeby wiedzieć, które z nich kosztują tygodnie odrabiania strat, a które da się utrzymać mechanicznie.
Ten artykuł to lista kontrolna. Nie zastępuje filaru usługi headless WordPress, gdzie stoją argumenty architektoniczne. To jest to, co sprawdzamy przed każdą migracją, w jej trakcie i po niej, w tej kolejności.
W skrócie
- Zachowuj adresy kanoniczne na poziomie całego URL, nie samego sluga.
- Zachowuj hreflang w HTML, nie tylko w mapie witryny.
- Renderuj meta tagi i JSON-LD na serwerze, nie w przeglądarce.
- Przenieś historię przekierowań przed zmianą adresów, nie po niej.
- Trzymaj jedną mapę witryny jako źródło prawdy, nie dwie.
- Ukryj origin WordPressa przed indeksem wyszukiwarek.
- Przenieś teksty alternatywne grafik i dane strukturalne obrazów.
Jak zachować adresy kanoniczne przy migracji do headless
Adres kanoniczny to obietnica. Liczy na nią każdy link zewnętrzny, każdy wpis w indeksie Google, każde udostępnienie w mediach społecznościowych. Migracja do headless, która po cichu obcina segment ścieżki, zmienia wielkość liter albo kolejność parametrów zapytania, właśnie złamała każdą z tych obietnic, i to bez śladu.
Dwie zasady. Po pierwsze, zanim dotkniesz frontendu, zbierz pełny zestaw adresów kanonicznych ze starej instalacji WordPressa. Eksportujemy każdy opublikowany wpis, stronę i stronę taksonomii z adresem, który Google ma w indeksie; to jest źródło prawdy. Po drugie, wpisz adres kanoniczny do odpowiedzi HTML frontendu headless, a nie do modyfikacji <head> wykonywanej po stronie klienta. Silniki generatywne i silniki odpowiedzi parsują początkowy HTML; meta tagi zmieniane w przeglądarce dla nich nie istnieją.
Jeśli musisz zmienić adres, zrób przekierowanie 301 ze starego na nowy i utrzymuj je przez co najmniej rok.
Tagi hreflang w HTML headless WordPress
Wielojęzyczne strony na WordPressie zarządzają tłumaczeniami przez WPML, Polylang albo własne rozwiązanie. W bazie danych mapowanie jest poprawne. Frontend headless musi potem wyrenderować <link rel="alternate" hreflang="..."> dla każdej wersji językowej w odpowiedzi HTML.
Wzorzec, który umyka większości agencji: hreflang musi wskazywać także na samą stronę. Strona angielska wymienia siebie i wszystkie przetłumaczone alternatywy. Strona polska wymienia siebie i wszystkie alternatywy. Obie listy się zgadzają. Poradnik Google o zlokalizowanych wersjach wymaga, by każda wersja językowa wymieniała siebie i wszystkie pozostałe, a gdy dwie strony nie wskazują na siebie nawzajem, Google ignoruje tagi.
Generowanie hreflang traktujemy jako część buildu, nie decyzję w czasie działania. Mapa ścieżek jest liczona przy buildzie i hashowana, a każdy dryf wywala build.
Renderowanie meta tagów i JSON-LD po stronie serwera
Najczęstszy regres SEO, jaki widzieliśmy w migracjach do headless: meta tagi i JSON-LD wstawiane JavaScriptem po załadowaniu strony. Przeglądarka je widzi. Googlebot czasem je widzi. Silniki generatywne, asystenci głosowi i większość crawlerów LLM zwykle nie.
Dwie zasady. Renderuj meta tagi, adres kanoniczny, Open Graph i każdy blok JSON-LD Schema.org w początkowej odpowiedzi HTML. W Astro to zachowanie domyślne. W Next.js oznacza to renderowanie na serwerze (metadane App Routera albo starsza ścieżka getServerSideProps), a nie poleganie na ponownym uruchomieniu next/head w przeglądarce.
To samo dotyczy grafik: element <img> z alt i src w HTML da się zaindeksować. <img> wstrzyknięty po efekcie po stronie klienta jest niewidoczny dla większości crawlerów i dla potoków danych treningowych AI.
Ponowne użycie JSON-LD z Yoast w headless WordPress
WordPress z Yoast SEO albo Rank Math już generuje dobry JSON-LD dla Article, Product i Organization. Przy migracji do headless kusi, żeby napisać go od zera na frontendzie. Nie ulegaj.
Odczytaj istniejący JSON-LD z originu WordPressa przez endpoint REST albo GraphQL. Przekaż go dalej. Dodaj tylko to, co frontend faktycznie wie, a WordPress nie (na przykład znaczniki czasu buildu dla dateModified, jeśli Twój proces redakcyjny nie rusza dat). Dwa systemy generujące nakładający się JSON-LD to prosta droga do błędów w raportach wyników z elementami rozszerzonymi w Search Console.
Na własnych stronach używamy komponentów z fazy 0 w src/components/seo/: DirectAnswer, FAQ i Quote. Każdy emituje własny, minimalny JSON-LD, który nie nakłada się na schemat Article całej strony.
Jak przenieść przekierowania przed zmianą adresów URL
Kolejność ma znaczenie. Lista kontrolna przed migracją zbiera każde przekierowanie wewnętrzne i zewnętrzne, także te ciche (/wp-content/... do /uploads/..., przekierowania według kodu kraju, warianty AMP). Nowy frontend wypuszcza te przekierowania w dniu zero, zanim publiczny ruch przejdzie na nowe adresy.
Na Cloudflare Pages trzymamy plik _redirects poniżej limitu platformy wynoszącego 2000 reguł. Build, który przekroczyłby limit, nie przechodzi. Wszystko, co wymaga więcej niż 2000 reguł, trafia zamiast tego do Workera z logiką przekierowań z parametrami.
Gdy publiczny DNS w końcu przełącza się na nowy frontend, żadne przekierowanie nie jest na produkcji nowe: każda reguła była testowana w buildzie przez tygodnie przed przełączeniem.
Jedna mapa witryny XML dla headless WordPress
WordPress 5.5 dodał domyślną mapę /wp-sitemap.xml. Yoast SEO i Rank Math dodają własne. Framework frontendu headless też generuje mapę witryny. Trzy mapy w jednej domenie to przepis na to, żeby Search Console wyrywało sobie włosy z głowy.
Zasada: wybierz jedną kanoniczną mapę witryny, a pozostałe wyłącz albo przekieruj. Zwykle generujemy mapę we frameworku frontendu, żeby adresy dokładnie zgadzały się z publiczną stroną, a mapę z originu WordPressa przekierowujemy 301 na tę z frontendu. Origin WordPressa staje się wtedy niewidoczny dla wyszukiwarek.
Jak ustawić noindex na backendzie headless WordPress
Migracja do headless zostawia origin WordPressa w działaniu, zwykle na subdomenie albo prywatnym hoście. Nadal serwuje wyrenderowany HTML, ma działającą mapę witryny i odpowiada na zapytania REST. Wyszukiwarki, które znajdą ten origin, zaindeksują go jako duplikat publicznej strony, a ten duplikat nie będzie tym, który się pozycjonuje.
Trzy zabezpieczenia. robots.txt originu blokuje wszystkie ścieżki poza endpointami REST i GraphQL. Origin wysyła X-Robots-Tag: noindex, nofollow w nagłówkach HTTP dla każdej odpowiedzi HTML. Mapa witryny originu jest usunięta albo zwraca 410.
Jeśli origin działa w tej samej domenie co publiczna strona, pod prefiksem ścieżki (na przykład /wp-admin/ albo /wp/), te same zabezpieczenia stosujesz w zakresie tych ścieżek.
Jak Google przetwarza JavaScript we frontendzie headless
Według podstaw SEO dla JavaScriptu w Google Search Central Google przetwarza aplikacje internetowe oparte na JavaScripcie w trzech fazach: skanowanie (crawling), renderowanie i indeksowanie. Googlebot kolejkuje do renderowania każdą stronę, która zwraca kod 200, chyba że metatag lub nagłówek robots zabrania indeksowania. Strona może czekać w tej kolejce kilka sekund, ale dokumentacja zastrzega, że może to potrwać dłużej.
Dla frontendu headless wynika z tego konkretna rzecz. To, co przychodzi w początkowej odpowiedzi HTML, jest przetwarzane w pierwszym przebiegu; to, co zależy od JavaScriptu, czeka na renderowanie. Ten sam poradnik mówi, że renderowanie po stronie serwera albo prerenderowanie nadal ma sens, bo przyspiesza stronę dla użytkowników i robotów, a nie każdy bot potrafi uruchomić JavaScript. Dlatego początkowy HTML traktujemy jako kontrakt SEO każdej trasy: tytuł, meta opis, canonical, hreflang, linki i JSON-LD muszą w nim być bez wykonywania czegokolwiek.
Obrazy podlegają tej samej logice. Poradnik Google o leniwym ładowaniu wymienia wbudowane w przeglądarkę leniwe ładowanie i API IntersectionObserver jako metody przyjazne wyszukiwarce, bo ładują treść, gdy jest widoczna w obszarze widoku, i nie zależą od przewijania ani klikania, których Google Search nie wykonuje. Odradza też leniwe ładowanie obrazów, które użytkownik widzi od razu. Sprawdzenie odbywa się na wyrenderowanym HTML: adres obrazu musi siedzieć w atrybucie src elementu <img>, co pokazuje narzędzie do sprawdzania adresów URL w Search Console.
Dlaczego renderowanie dynamiczne nie rozwiązuje SEO w headless WordPress
Przez lata szybkim wyjściem dla aplikacji SPA podpiętej pod WordPressa było renderowanie dynamiczne: botom serwuje się prerenderowany HTML, użytkownikom aplikację w JavaScripcie. Google Search Central opisuje je dziś jako obejście, a nie zalecane rozwiązanie i w zamian zaleca renderowanie po stronie serwera, renderowanie statyczne albo hydratację.
Ta sama strona wyjaśnia, że Googlebot zasadniczo nie uznaje renderowania dynamicznego za cloaking, ale serwowanie użytkownikom i robotom zupełnie innej treści może zostać za cloaking uznane. W projekcie headless oznacza to utrzymywanie dwóch ścieżek renderowania, które muszą dawać tę samą treść, a nikt nie zauważa, kiedy się rozjadą. Frontend renderujący na serwerze albo w czasie buildu usuwa ten dubel: użytkownik, Googlebot i robot bez JavaScriptu dostają ten sam HTML.
Kody statusu HTTP i soft 404 we frontendzie headless
Gdy frontend pyta REST API WordPressa o slug, który nie istnieje, dostaje pustą kolekcję, a nie błąd. Jeśli na tej podstawie wyrenderuje szablon “nie znaleziono” z kodem 200, Googlebot wstawi go do kolejki renderowania jak każdą poprawną stronę i skończy się to soft 404. Przy renderowaniu po stronie serwera poprawka jest prosta: pusta odpowiedź oznacza, że frontend zwraca prawdziwe 404, albo 410, gdy treść usunięto celowo.
To samo dotyczy szkiców i wpisów prywatnych. Według dokumentacji endpointu wpisów parametr status kolekcji ma domyślnie wartość publish, więc wycofany wpis znika z odpowiedzi, a jego trasa musi odpowiedzieć kodem błędu, nie pustą stroną.
Dla tras rozwiązywanych wyłącznie w przeglądarce poradnik Google podaje dwie strategie: przekierowanie JavaScriptem na adres, pod którym serwer odpowiada kodem 404 (na przykład /not-found), albo dodanie JavaScriptem <meta name="robots" content="noindex"> do stron błędów.
Canonical, noindex i linki ustawiane JavaScriptem
Ten sam poradnik Google ustala trzy zasady, które w headless łatwo złamać.
- Canonical. Jeśli canonical ustawiasz JavaScriptem, musi mieć zawsze tę samą wartość co w oryginalnym HTML; sprzeczne lub wielokrotne tagi
rel="canonical"mogą dać nieoczekiwane wyniki. W headless zdarza się to wtedy, gdy HTML z serwera niesie canonical z originu WordPressa, a komponent kliencki przepisuje go na adres frontendu. - Noindex. Gdy Google trafi na
noindex, może pominąć renderowanie i wykonanie JavaScriptu. Frontend, który wysyłanoindexw początkowym HTML na czas ładowania danych i zdejmuje go później JavaScriptem, zostawia stronę poza indeksem. Jeśli strona ma być zaindeksowana, oryginalny kod nie może zawieraćnoindex. - Linki. Google odkrywa tylko linki będące elementami
<a>z atrybutemhref, a poradnik każe nie ładować innej treści przez fragmenty adresu, tylko przez History API. Menu nawigujące przez handleryonClickalbo trasy typu#/wpiszostawia nieprzeszukaną strukturę wewnętrzną, którą miał WordPress.
Jak wyciągnąć inwentarz adresów URL z REST API WordPressa
Pierwsza zasada tego artykułu każe wyeksportować wszystkie opublikowane adresy, zanim ruszysz frontend. REST API robi to bez wtyczek. Według podręcznika paginacji REST API endpointy kolekcji zwracają ograniczoną liczbę elementów na żądanie; per_page przyjmuje liczbę całkowitą od 1 do 100, a page wskazuje, którą stronę wyników zwrócić. Każda odpowiedź zawiera nagłówek X-WP-Total z łączną liczbą rekordów i X-WP-TotalPages z łączną liczbą stron. Wystarczy odczytać X-WP-TotalPages przy pierwszym żądaniu do /wp-json/wp/v2/posts?per_page=100 i przejść po pozostałych stronach.
Z dokumentacji wpisów przy każdym wpisie liczą się dwa pola: link, czyli adres wpisu w postaci, w jakiej publikuje go WordPress, oraz modified_gmt, data ostatniej modyfikacji w GMT. Pierwsze to lista, z którą porównujesz każdą trasę nowego frontendu; drugie zasila lastmod w mapie witryny. Argument modified_after ogranicza odpowiedź do wpisów zmienionych po dacie w formacie ISO 8601, co pozwala na buildy przyrostowe bez pobierania całej treści za każdym razem. Ten sam wzorzec paginacji działa dla /wp/v2/pages i endpointów taksonomii.
Rozwiązywanie adresów, podglądy i cache w WPGraphQL
WPGraphQL to darmowa wtyczka open source, która daje WordPressowi rozszerzalny schemat i API GraphQL. Dokumentacja pokazuje, jak pobrać wpis po jego URI, czyli ścieżce wpisu, użytej jako identyfikator. Dla SEO ma to znaczenie, bo publiczna trasa i treść pochodzą z jednego źródła: frontend prosi o węzeł po ścieżce, którą dostał, i nie potrzebuje własnej tabeli tłumaczącej trasy na identyfikatory.
Podglądy wymagają więcej uwagi. Według dokumentacji podglądów WPGraphQL potrafi zwrócić nieopublikowaną wersję wpisu, ale żądanie musi być uwierzytelnione metodą, którą WordPress rozpoznaje. Jeśli nie jest, WPGraphQL zwraca opublikowany węzeł tak, jakby o podgląd nikt nie prosił, bez błędu. Trasa podglądu z zepsutym uwierzytelnianiem po cichu pokazuje opublikowaną treść. Dlatego trasy podglądu mają noindex, są poza mapą witryny i omijają publiczny cache, a pole isPreview pozwala sprawdzić, czy podgląd naprawdę został zastosowany.
Cache decyduje, jak długo zmiana tytułu, opisu albo canonical zrobiona w WordPressie idzie do HTML. Dokumentacja wydajności podaje, że WPGraphQL obsługuje żądania GET, które może cache’ować CDN albo proxy, a rozszerzenie WPGraphQL Smart Cache oznacza odpowiedzi nagłówkiem X-GraphQL-Keys i przy zmianie treści emituje zdarzenie z listą dotkniętych tagów, żeby host wyczyścił tylko te dokumenty. Dzięki temu redakcyjna zmiana metadanych SEO trafia do frontendu bez czekania, aż cache wygaśnie.
Powiązane poradniki o SEO w headless WordPress
Ten artykuł rozwija temat ze strony usługi headless WordPress. Jeśli stoisz przed wyborem technologii, zobacz Headless WordPress, Next.js czy Astro 2026. Szerszy obraz widoczności, łącznie z cytowaniami w LLM, opisuje przewodnik po widoczności w AI i LLM: to nasze kanoniczne ujęcie tego, co wdrażamy w ramach AEO i GEO na tych fundamentach SEO.







