Headless WordPress, ISR czy SSR: tryb renderowania dobierany do rytmu zmian treści
Pytanie “ISR czy SSR” ma sens tylko w odniesieniu do konkretnej trasy. Nie ma jednej odpowiedzi dla całego serwisu. Astro i Next.js pozwalają wybrać tryb na poziomie strony albo layoutu, a dojrzałe podejście inżynierskie polega na świadomym wyborze, trasa po trasie, w oparciu o model tego, jak często zmienia się treść.
Ten artykuł należy do filaru usługi headless WordPress i uzupełnia macierz decyzyjną Next.js czy Astro, która opisuje wybór na poziomie frameworka.
W skrócie
- ISR (albo strona statyczna z rewalidacją) wygrywa, gdy rytm zmian treści jest przewidywalny, a ruch duży.
- SSR wygrywa, gdy strona jest personalizowana, zależy od sesji albo zawiera dane na żywo.
- O poprawności ISR decyduje unieważnianie cache; webhooki są lepsze niż rewalidacja czasowa.
- Cloudflare Workers może serwować oba tryby przez adapter frameworka; strona z cache nie zużywa prawie wcale CPU, SSR płaci za pełne renderowanie.
- Punktem wyjścia jest najtańszy tryb, który zapewnia poprawność; na SSR przechodzisz tylko wtedy, gdy to konieczne.
SSG, ISR i SSR w kontekście WordPressa
Statyczne generowanie stron (SSG). Strona powstaje raz, podczas builda, i jest serwowana jako zwykły HTML. Najtańsza w momencie żądania, najwolniejsza w aktualizacji.
Incremental Static Regeneration (ISR). Strona powstaje raz, ale można ją wygenerować ponownie po wyzwalaczu, zwykle webhooku przy publikacji albo po upływie interwału rewalidacji. Tania w momencie żądania, aktualizacja z ostateczną spójnością.
Server-Side Rendering (SSR). Strona jest renderowana przy każdym żądaniu. Zawsze świeża, ale koszt działania rośnie razem z ruchem. Personalizacja, uwierzytelnianie i dane na żywo pasują tu naturalnie.
W headless WordPress wszystkie trzy tryby czytają dane z originu WordPressa przez REST albo GraphQL. Różnią się tym, kiedy to robią.
Kiedy ISR, a kiedy SSR w headless WordPress
Liczą się dwa czynniki:
Rytm zmian treści. Jak często ta strona się zmienia? Raz na kwartał, raz dziennie, co minutę, w czasie rzeczywistym?
Zakres personalizacji. Czy strona różni się w zależności od odwiedzającego? Stan zalogowania, ceny zależne od lokalizacji, wariant testu A/B.
Reguła: wybierz najtańszy tryb, który zapewnia poprawność. Statyczny jest najtańszy. SSR najdroższy. Przesuwaj się w stronę SSR tylko wtedy, gdy tańszy tryb nie daje poprawnego wyniku.
| Typ strony | Tryb domyślny | Dlaczego |
|---|---|---|
| Strony marketingowe, wpisy na blogu | Statyczny (przebudowa przy publikacji) | Rzadkie zmiany, brak personalizacji |
| Archiwa kategorii i tagów | ISR z webhookiem publikacji | Rytm zależy od publikacji treści |
| Strony produktów, stabilny katalog | ISR z webhookiem stanów magazynowych | Przewidywalne unieważnianie |
| Strony produktów, stany w czasie rzeczywistym | SSR z cache na brzegu | Stany zmieniają się w ciągu sekund |
| Koszyk i checkout | SSR | Z definicji zależą od sesji |
| Panel po zalogowaniu | SSR | Stan per użytkownik |
| Redakcyjna strona główna | ISR z webhookiem publikacji | Rytm zależy od zdarzeń publikacji |
Unieważnianie cache ISR webhookami z WordPressa
ISR wydaje się darmowe, dopóki po zmianie sluga nie zacznie serwować nieaktualnego adresu kanonicznego. Wzorzec, który temu zapobiega:
Unieważnianie sterowane webhookami. Origin WordPressa wysyła webhook przy publikacji, zmianie sluga albo usunięciu wpisu. Framework frontendowy odbiera webhook i uruchamia regenerację stron, których zmiana dotyczy. Koszt to jedna integracja webhooka po stronie originu WordPressa, ponoszony raz.
Rewalidacja czasowa wyłącznie jako zabezpieczenie. Interwał rewalidacji 60 sekund pokrywa przypadki, gdy webhook nie dotrze, ale nie powinien być głównym wyzwalaczem. Interwał 60 sekund nie oznacza przebudowy każdej strony 60 razy na godzinę: Next.js regeneruje stronę dopiero wtedy, gdy po upływie okna przyjdzie żądanie. Prawdziwy koszt polega na tym, że każda popularna strona może się regenerować w tle nawet raz na minutę, a na platformach rozliczanych za żądanie ta praca w tle liczy się jako dodatkowe zużycie mocy obliczeniowej.
Tagi cache zamiast adresów URL. Każda strona w cache dostaje tag z ID wpisu w WordPressie, ID terminów taksonomii, do których się odwołuje, oraz tagi przekrojowe (strona główna, sitemap). Gdy przychodzi webhook, front czyści cache po tagu, a nie po adresie. To różnica między “wygeneruj ponownie stronę produktu” (kruche) a “wygeneruj ponownie wszystko, co odwołuje się do produktu 8421” (poprawne).
Koszt ISR i SSR na Cloudflare Workers
Astro trafia na Workers przez oficjalny adapter Cloudflare. Next.js dokumentuje ISR dla środowiska Node.js, a obsługę przez adaptery opisuje jako zależną od platformy, więc przed zaplanowaniem ISR na Workers sprawdź adapter. Koszty w podziale na tryby:
- Statyczne strony na brzegu. Cloudflare Pages serwuje zwykły HTML przy prawie zerowym zużyciu CPU na żądanie. Najtańszy tryb.
- ISR. Pierwsze żądanie po unieważnieniu płaci pełny koszt renderowania; żądania z cache prawie nic. Na Workers zależy to od cache adaptera, a nie od samego frameworka.
- SSR. Każde żądanie płaci na Workers pełny koszt renderowania. Przewidywalnie per żądanie, drogo przy dużej skali.
Różnica kosztów ma znaczenie przy dużym ruchu. Przy małym ruchu o wyborze decyduje poprawność, nie koszt.
Przykłady ISR i SSR dla prawdziwych tras WordPressa
Marketingowa strona główna. Statyczna, przebudowywana webhookiem przy każdej publikacji redakcyjnej. Cache na brzegu przez 24 godziny z możliwością ręcznego wyczyszczenia. SSR jako wyjście awaryjne tylko wtedy, gdy dojdzie baner zależny od kraju.
Karta produktu w WooCommerce. ISR z kluczem w postaci ID produktu. Webhook z WooCommerce przy zmianie stanu, ceny albo treści. Okno cache: 1 godzina jako zabezpieczenie. SSR tylko wtedy, gdy wyświetlanie stanów w czasie rzeczywistym jest wymogiem UX.
Historia zamówień klienta. SSR. Dane per użytkownik, zależne od sesji, bez cache na brzegu.
Ta sama architektura, trzy różne tryby renderowania, jedna reguła decyzyjna.
Jak działa revalidate w Next.js dla stron z WordPressa
Trasa z export const revalidate = 3600 serwuje zapisany w cache HTML każdemu żądaniu w obrębie okna. Aktualny przewodnik ISR w dokumentacji Next.js opisuje, co dzieje się dalej: pierwsze żądanie po upływie okna nadal dostaje nieaktualną stronę, to żądanie uruchamia regenerację w tle, a gdy ta się powiedzie, kolejne żądanie dostaje nową wersję. Wpisu, którego nikt nie odwiedza, Next.js nigdy nie regeneruje. Interwał ogranicza, jak często strona może się przebudować, ale niczego nie planuje.
O tym, czy to zadziała na froncie WordPressa, decydują cztery szczegóły z tego samego przewodnika:
- Gdy trasa wykonuje kilka wywołań fetch z różnymi wartościami
revalidate, najniższa z nich ustala interwał ISR całej strony. Pasek boczny, który pobiera najnowsze wpisy z oknem 60 sekund, ściąga cały artykuł do 60 sekund. - Jeden fetch z
revalidate: 0albocache: 'no-store'czyni trasę dynamiczną. Pojedyncze wywołanie endpointu WordPressa bez cache, na przykład licznika wyświetleń, po cichu przenosi stronę z ISR do SSR. - Jeśli regeneracja rzuci błąd, na przykład dlatego, że origin WordPressa zwrócił błąd, Next.js dalej serwuje ostatnią poprawną wersję i ponawia próbę przy następnym żądaniu. Awaria originu nie czyści frontu.
- Przewodnik zaleca długi interwał, raczej godzinę niż sekundę, oraz rewalidację na żądanie tam, gdzie potrzebna jest precyzja. To ta sama zasada “najpierw webhook” co w sekcji wyżej.
Nagłówek odpowiedzi x-nextjs-cache podaje stan każdego żądania jako HIT, STALE, MISS albo REVALIDATED. Odczytanie go zaraz po publikacji to najszybszy sposób, żeby potwierdzić, że webhook dotarł do frontu.
Jak rewalidować strony Next.js webhookiem z WordPressa
Stroną odbierającą jest Route Handler, na przykład app/api/revalidate/route.ts, który sprawdza wspólny sekret i wywołuje jedną z dwóch funkcji z next/cache.
revalidateTag(tag, 'max') oznacza jako nieaktualny każdy zapisany fetch z tym tagiem, na wszystkich stronach, które go używają (dokumentacja revalidateTag). Z profilem 'max' odwiedzający dalej dostają starą kopię, a świeża ładuje się w tym czasie. Przy publikacji w WordPressie to zwykle właściwy kompromis: wywołania fetch dostają tagi post-8421 i posts, a jeden webhook odświeża wpis, archiwum i stronę główną. Gdy starej kopii nie wolno już nigdy pokazać, na przykład po wycofaniu artykułu, dokumentacja każe wywołującym z webhooka przekazać { expire: 0 }, bo updateTag działa tylko w Server Actions. Forma z jednym argumentem, revalidateTag(tag), jest przestarzała. Tagi rozróżniają wielkość liter i mają limit 256 znaków.
revalidatePath(path) działa na trasach, a nie na danych (dokumentacja revalidatePath). Dosłowna ścieżka, taka jak /blog/my-post, odświeża jedną stronę; wzorzec, taki jak /blog/[slug], wymaga drugiego argumentu, 'page' albo 'layout'. W headless WordPress często zdarzają się dwie pułapki. Przy rewrites w next.config.js przekazujesz ścieżkę docelową, a nie adres widoczny dla odwiedzającego. A Proxy nie uruchamia się przy żądaniach ISR na żądanie, więc ścieżkę przepisaną w Proxy trzeba rewalidować w jej ostatecznej postaci.
Obie funkcje tylko oznaczają wpisy; regeneracja następuje przy kolejnej wizycie. Gdy aplikacja działa na kilku instancjach z domyślnym cache w systemie plików, webhook unieważnia tylko tę instancję, która go odebrała. Rewalidacja na żądanie potrzebuje wtedy wspólnego cache handlera, zanim będzie można na niej polegać.
Jak route caching w Astro 7 zastępuje ISR
Astro domyślnie renderuje każdą stronę z wyprzedzeniem. Trasa przechodzi na renderowanie na żądanie dzięki export const prerender = false, co wymaga adaptera serwerowego. output: 'server' odwraca to ustawienie domyślne, a dokumentacja radzi zostać przy 'static', dopóki większość stron naprawdę nie potrzebuje serwera (renderowanie na żądanie w Astro). Strona renderowana na żądanie może ustawić własny nagłówek Cache-Control przez Astro.response.headers.
Astro 7 wprowadza route caching, który zamyka większość luki wobec ISR w Next.js (route caching w Astro). Strona renderowana na żądanie wywołuje Astro.cache.set({ maxAge: 3600, swr: 60, tags: ['post-8421'] }). Endpoint odbierający webhook z WordPressa wywołuje context.cache.invalidate({ tags: ['post-8421'] }) albo invalidate({ path: '/blog/my-post' }). Unieważnianie po ścieżce działa tylko przy dokładnym dopasowaniu, bez symboli wieloznacznych. routeRules w astro.config.mjs ustawiają wartości domyślne dla grup tras, na przykład maxAge dla /blog/[...slug], a strona może je nadpisać albo wyłączyć cache przez Astro.cache.set(false), gdy odpowiedź jest spersonalizowana.
Sam cache zapewnia dostawca. Wbudowany memoryCache() pasuje do pojedynczego serwera Node. Adaptery Netlify, Vercel i Cloudflare mają dostawców CDN, którzy są jeszcze eksperymentalni i trzeba ich włączyć ręcznie. Dostawca Cloudflare, od @astrojs/cloudflare 14, ustawia nagłówki Cloudflare-CDN-Cache-Control i Cache-Tag oraz włącza cache Workers. W trybie deweloperskim API cache nic nie robi, więc testuj je na buildzie i podglądzie.
Jak łączyć statyczne i dynamiczne części na jednej stronie WordPressa
Wiele tras jest stabilnych poza jednym elementem na żywo: strona produktu ze stanem magazynu, artykuł z awatarem zalogowanego czytelnika. Renderowanie całej strony przy każdym żądaniu dla tego jednego elementu to drogie rozwiązanie. Server islands w Astro to tańsze (server islands w Astro). Dodaj server:defer do komponentu, który potrzebuje świeżych danych. Reszta strony zostaje wyrenderowana z wyprzedzeniem albo w cache, a wyspa jest pobierana z własnego endpointu po załadowaniu strony. Do tego czasu w jej miejscu widać element potomny z slot="fallback", a każda wyspa ładuje się niezależnie, więc wolne zapytanie o stan magazynu niczego innego nie wstrzymuje.
Obowiązują dwa ograniczenia. Server islands wymagają adaptera, a ich propsy muszą być serializowalne: zwykłe obiekty, ciągi znaków, liczby, tablice, daty i kilka innych typów, ale nie funkcje. Na stronie produktu WooCommerce oznacza to przekazanie ID produktu, a nie klienta API.
To doprecyzowuje tabelę wyżej. Trasa zostaje statyczna albo w ISR, a na każde żądanie renderuje się tylko wyspa.
Jak pobierać treści z WordPressa do buildów statycznych i ISR
Każdy krok prerenderingu zaczyna się od listy slugów, a REST API WordPressa zwraca ją stronami. per_page przyjmuje wartości od 1 do 100, a każda odpowiedź zawiera nagłówki X-WP-Total i X-WP-TotalPages, więc skrypt buildu czyta pierwszą stronę i iteruje do liczby stron z nagłówka (paginacja w REST API). Next.js przekazuje tę listę do generateStaticParams. Wpisy opublikowane po buildzie renderują się przy pierwszym żądaniu, o ile dynamicParams nie jest ustawione na false, a generateStaticParams nie jest wywoływane ponownie podczas rewalidacji (dokumentacja generateStaticParams).
Parametr _fields przycina odpowiedź do tego, czego potrzebuje dany krok, na przykład ?_fields=slug,modified_gmt dla listy slugów, a WordPress w ogóle nie wylicza pominiętych pól. Od WordPressa 5.3 przyjmuje też zagnieżdżone klucze meta, a od 5.4 _embed można ograniczyć do nazwanych relacji, takich jak author,wp:term (parametry globalne REST API).
Do wykrywania zmian endpoint wpisów przyjmuje modified_after z datą w formacie ISO 8601 i zwraca modified_gmt przy każdym wpisie (dokumentacja endpointu posts). Zadanie cykliczne, które pyta o wpisy zmienione od swojego ostatniego uruchomienia, to rozsądne zabezpieczenie na wypadek zgubionych webhooków i tańsze niż krótkie okno revalidate na każdej trasie.
Jak pokazać podgląd szkiców WordPressa na froncie z ISR
Redaktorzy oczekują, że przycisk “Podgląd” pokaże szkic, a front z cache serwuje wersję opublikowaną. Draft Mode w Next.js rozwiązuje to dla pojedynczego odwiedzającego (przewodnik Draft Mode). Route Handler sprawdza wspólny sekret, wywołuje draftMode().enable(), które ustawia ciasteczko __prerender_bypass, i przekierowuje na stronę. Żądania z tym ciasteczkiem omijają cache fetch i cache odpowiedzi ISR, a odpowiedź idzie z Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate. Wszyscy pozostali odwiedzający dalej dostają stronę z cache.
Po stronie WordPressa endpoint wpisów bez dodatkowych parametrów zwraca treści opublikowane, bo status ma domyślnie wartość publish. Szkice odczytuje się więc uwierzytelnionymi żądaniami. Hasła aplikacji, wbudowane w WordPressa od wersji 5.6 i wysyłane jako Basic Auth przez HTTPS, to metoda opisana w podręczniku (uwierzytelnianie w REST API). Trzymaj te dane dostępowe na serwerze, w Route Handlerze albo w fetch wykonywanym tylko po stronie serwera, nigdy w kodzie klienta.
Powiązane poradniki o headless WordPress
Ten artykuł należy do filaru usługi headless WordPress. Wybór na poziomie frameworka opisuje macierz decyzyjna Next.js czy Astro. Ryzyka migracji zbiera lista wzorców SEO dla headless WordPress, specyfikę sklepu omawia headless WordPress dla WooCommerce, a stronę kosztową artykuł o ekonomii headless WordPress. Porównanie frameworków wersja po wersji, łącznie z tym, jak każdy z nich pobiera dane z WordPressa, znajdziesz w tekście Astro 5 vs Next.js 15.
Wdrożeniową stronę tego tematu prowadzę w ramach usługi audyt Core Web Vitals.







