Połącz WordPressa z Next.js App Router przez API. Poznaj bezpieczną konfigurację produkcyjną bez problemów z ISR, trybem podglądu i webhookami.
Maciej Sala
Founder StriveLab
7 min czytaniaOpublikowano 11 kwietnia 2026 (Aktualizacja 17 lipca 2026)
WPGraphQL i Next.js App Router: zakres implementacji headless WordPress
Jeśli decyzja ze wstępu jeszcze nie zapadła, zacznij od Headless WordPress — kiedy ma sens, a kiedy nie.
Artykuł jest technicznym tutorialem wdrożeniowym, który mówi jak połączyć WordPress, i Next.js w działającą architekturę. Headless WordPress to WordPress używany wyłącznie jako , z Next.js jako frontendem.
WordPress udostępnia treści przez ( lub GraphQL), a Next.js pobiera je, renderuje i serwuje użytkownikowi. W efekcie znany edytor WordPress dla klienta łączy się z wydajnością i Next.js na frontendzie.
Kiedy headless WordPress z Next.js ma sens?
Istniejący WordPress z setkami artykułów. Klient nie chce migrować treści,
ale chce szybszy frontend.
Zespół redakcyjny zna WordPress i nie chce uczyć się Sanity/Strapi.
Zaawansowane pola ACF (Advanced Custom Fields). Pola własne, elastyczna
zawartość (flexible content) oraz grupy pól.
WooCommerce. Koszyk i checkout w wariancie headless z Next.js, ale
zarządzanie produktami w WordPressie.
Kiedy zamiast WordPressa wybrać dedykowany ?
Nowy projekt od zera. Sanity/Strapi mają lepsze doświadczenie deweloperskie
i nie ciągniesz ze sobą PHP.
Nie potrzebujesz ekosystemu wtyczek WP. Mniej zależności oznacza mniej
problemów.
Jeden deweloper. Utrzymanie WordPress i Next.js to dwa systemy do
zarządzania.
Konfiguracja WPGraphQL w WordPressie i Next.js
Code
# W WordPress — zainstaluj wtyczki:# 1. WPGraphQL (wp-graphql)# 2. WPGraphQL for ACF (opcjonalnie, jeśli używasz ACF)
Po instalacji WPGraphQL: endpoint dostępny na https://twoj-wordpress.pl/graphql.
Klient GraphQL w Next.js App Router
Code
// lib/wordpress.tsimport 'server-only'const WORDPRESS_API_URL = process.env.WORDPRESS_API_URL!interface GraphQLResponse<T> { data?: T errors?: { message: string; path?: (string | number)[] }[]}export async function fetchGraphQL<T>( query: string, variables?: Record<string, unknown>, options: { tags?: string[]; revalidate?: number; authToken?: string } = {},): Promise<T> { const res = await fetch(WORDPRESS_API_URL, { method: 'POST', headers: { 'Content-Type': 'application/json', ...(options.authToken && { Authorization: `Bearer ${options.authToken}`, }), }, body: JSON.stringify({ query, variables }), next: { revalidate: options.revalidate ?? 3600, tags: options.tags, }, }) if (!res.ok) { throw new Error(`WordPress GraphQL HTTP ${res.status}`) } const json = (await res.json()) as GraphQLResponse<T> // GraphQL może zwrócić HTTP 200 razem z errors i częściowym data. if (json.errors?.length || json.data === undefined) { throw new Error( json.errors?.map((error) => error.message).join(', ') || 'WordPress GraphQL response has no data', ) } return json.data}
Unikaj dołączania tokenów Bearer bez wyraźnej potrzeby. Dane publiczne pobieraj anonimowo, a autoryzację stosuj wyłącznie w trybie podglądu. Pamiętaj, że WPGraphQL nie obsługuje natywnie, więc wymaga to dodatkowych wtyczek, takich jak WPGraphQL JWT Authentication lub WPGraphQL Headless Login. W komunikacji serwer-serwer możesz rozważyć użycie WordPress Application Passwords z uwierzytelnianiem Basic Auth, pamiętając o specyficznych ograniczeniach tego rozwiązania. Klucze dostępowe przechowuj wyłącznie w module server-only, dbaj o ich regularną rotację i nadawaj przypisanemu kontu minimalne uprawnienia, tylko takie, które są niezbędne do odczytu szkiców.
Typ generyczny T nie sprawdza odpowiedzi w czasie działania aplikacji. W większym projekcie generuj typy na podstawie schematu i dokumentów GraphQL (GraphQL Code Generator) oraz waliduj krytyczne granice, szczególnie gdy aktualizacja wtyczki może zmienić schemat. W CI uruchamiaj kontrolę dokumentów względem schematu ze środowiska staging przed wdrożeniem WordPressa.
WPGraphQL używa paginacji Relay opartej na kursorach. first: 1000 nie jest bezpiecznym skrótem: instalacja może ograniczyć maksymalny rozmiar strony, a jedno wielkie zapytanie obciąża PHP i bazę. Dla listy użytkowej pobieraj 10–100 elementów i wystaw „Następna strona” lub infinite scroll. Przy generateStaticParams rozważ pre-render tylko najpopularniejszych wpisów, a resztę generuj na żądanie — pełne przejście przez tysiące slugów wydłuża build i uzależnia wdrożenie od dostępności WordPressa.
Pola seo nie należą do WPGraphQL core. Jeśli chcesz pobierać metadane Yoast albo Rank Math, potrzebujesz kompatybilnego rozszerzenia, a nazwy pól musisz potwierdzić w swoim schemacie. Nie deklaruj ich wyłącznie w interfejsie TypeScript, bo typ nie sprawi, że serwer GraphQL zacznie je zwracać.
Sanityzacja HTML z WordPressa
WPGraphQL zwraca content i excerpt jako wyrenderowany HTML. WordPress filtruje treść zależnie od uprawnień autora, ale administrator może mieć unfiltered_html, wtyczka może dołożyć niebezpieczny markup, a przejęty CMS staje się źródłem . Przy dangerouslySetInnerHTML ustaw własną granicę zaufania i listę dozwolonych elementów.
Listę dozwolonych elementów dopasuj do bloków Gutenberga faktycznie używanych w projekcie. Osadzenia iframe, formularze, style inline i skrypty wymagają osobnej decyzji — nie dopuszczaj ich globalnie tylko dlatego, że jeden wpis ich potrzebuje. Dołóż restrykcyjne jako drugą warstwę ochrony.
Relatywny adres kanoniczny wymaga poprawnego metadataBase w głównym layoucie. W sytuacji, w której korzystasz z rozszerzenia SEO WordPressa, ustal jedno źródło prawdy dla tytułu, adresu kanonicznego, noindex i Open Graph. Nie mieszaj części pól z wtyczki z innymi, generowanymi niezależnie w Next.js. Pamiętaj też, by dla danych strukturalnych Article serializować bezpiecznie JSON (co najmniej zamień < na \u003c) i nie kopiuj w ciemno JSON-LD wygenerowanego dla starego frontendu WordPressa.
Powyższy wariant używa modelu cache bez cacheComponents, zgodnego z next.revalidate na fetch i segmentowym export const revalidate. Jeśli w Next.js 16 włączysz cacheComponents: true, konfiguracje segmentu takie jak revalidate są zastępowane przez jawne zakresy 'use cache', cacheLife i cacheTag. Wybór powinien być zapisany w next.config.ts i testowany po buildzie produkcyjnym.
Funkcja generateStaticParams nie ogranicza listy dostępnych adresów, dopóki nie ustawisz dynamicParams = false. W przypadku bloga zazwyczaj pozwala się na generowanie brakujących wpisów „na żądanie”. Pamiętaj jednak, że przy ustawieniu dynamicParams = false, nowy artykuł nie zadziała od razu po unieważnieniu cache’u przez webhook, bo będzie musiał zostać uwzględniony w kolejnym buildzie aplikacji.
Bezpieczny podgląd szkiców przez Next.js Draft Mode
Endpoint podglądu powinien dostać podpisany sekret i stabilny identyfikator wpisu, samodzielnie pobrać szkic z WPGraphQL oraz przekierować wyłącznie na slug zwrócony przez CMS. Nie przekierowuj wprost do parametru redirect lub slug z query string — powstaje open redirect albo możliwość wejścia na dowolną trasę.
Code
// app/api/draft/route.tsimport { draftMode } from 'next/headers'import { redirect } from 'next/navigation'export async function GET(request: Request) { const { searchParams } = new URL(request.url) const secret = searchParams.get('secret') const id = searchParams.get('id') if (secret !== process.env.DRAFT_SECRET || !id) { return new Response('Invalid preview request', { status: 401 }) } // Funkcja używa osobnego, serwerowego tokenu i cache: 'no-store'. // Sposób pobrania rewizji zależy od schematu oraz rozszerzeń WPGraphQL. const post = await getAuthenticatedPreviewPost(id) if (!post?.slug) return new Response('Draft not found', { status: 404 }) const draft = await draftMode() draft.enable() redirect(`/blog/${encodeURIComponent(post.slug)}`)}
Na stronie wpisu sprawdzaj status za pomocą await draftMode(). Jeśli podgląd jest aktywny, wywołaj uwierzytelnioną funkcję getAuthenticatedPreviewPost z opcją cache: 'no-store'. Dla standardowego ruchu korzystaj z cachowanego getPostBySlug. Pamiętaj, aby nigdy nie umieszczać tokenu WordPressa w zmiennych środowiskowych z prefiksem NEXT_PUBLIC_, komponentach klienckich ani w adresie URL podglądu. Stwórz endpoint wyłączający tryb podglądu i ustaw prefetch={false} dla linku wyjścia, aby uniknąć przypadkowego zaciągnięcia zawartości przez przeglądarkę.
Pamiętaj, że szkice mogą zawierać poufne informacje. Konto techniczne powinno mieć nadane minimalne uprawnienia (capabilities), a linki do podglądu powinny być podpisane czasowo lub mieć krótki okres ważności. Warto również logować próby dostępu i ograniczyć możliwość podglądu wyłącznie do zalogowanych redaktorów.
W WordPressie konfiguruj wysyłkę webhooków tylko dla kluczowych zmian statusu lub treści: odfiltruj automatyczne zapisy (autosave), rewizje oraz typy wpisów, które nie są wykorzystywane na frontendzie. Podpis HMAC obliczaj w oparciu o identyczne, surowe ciało żądania (raw body) oraz znacznik czasu. Dla event_id stosuj unikalne indeksy, a w środowisku produkcyjnym zaprojektuj mechanizm, który oznaczy nieudane próby obsługi jako gotowe do ponowienia, zamiast trwale blokować ponowienia.
revalidateTag(..., 'max') stosuje stale-while-revalidate, więc pierwszy odczyt może jeszcze dostać starą wartość. revalidatePath unieważnia wynik konkretnej trasy i pomaga przy publikacji lub wycofaniu wpisu. W sytuacji wymagania natychmiastowej widoczności zmiany, przetestuj strategię wygaszania dla używanego modelu cache.
Konfiguracja next/image dla mediów z WordPressa
Code
// next.config.tsconst nextConfig = { images: { remotePatterns: [ { protocol: 'https', hostname: 'twoj-wordpress.pl', pathname: '/wp-content/uploads/**', }, // Jeśli używasz CDN, dodaj jego konkretny hostname i pathname osobno. ], },}
Ogranicz remotePatterns do rzeczywistej ścieżki uploadów i konkretnych hostów . Szeroki wzorzec pozwala używać optymalizatora obrazów jako proxy dla większej liczby zasobów, niż potrzebujesz. W sytuacji, w której WordPress zwraca adresy po migracji domeny albo CDN przepisuje hostname, znormalizuj URL w jednej funkcji i przetestuj oba środowiska. Dla SVG zachowaj szczególną ostrożność i nie włączaj dangerouslyAllowSVG bez odpowiedniej polityki CSP i Content-Disposition.
Zapytuj o mediaDetails.width i height albo utrzymuj znane proporcje wariantu obrazu. Atrybut sizes jest potrzebny przy responsywnym layoucie, aby przeglądarka nie pobierała zbyt dużego pliku. next/image nie naprawi oryginału o wadze 15 MB ani braku właściwych wariantów po stronie biblioteki mediów.
Bezpieczeństwo i utrzymanie WPGraphQL na produkcji
Headless nie usuwa obowiązków WordPressa. Endpoint /graphql nadal uruchamia PHP i zapytania do bazy, a panel administracyjny pozostaje powierzchnią ataku. Aktualizuj WordPress, WPGraphQL i rozszerzenia, usuwaj nieużywane wtyczki, rób testowane backupy oraz monitoruj błędy i czas odpowiedzi. Oddziel staging od produkcji i sprawdzaj kompatybilność schematu przed aktualizacją.
Stosuj rozsądne limity paginacji, głębokości lub złożoności zapytań, limitowanie ruchu na odwrotnym proxy oraz trwały object cache, jeśli obciążenie tego wymaga. WPGraphQL Smart Cache może pomóc, ale nie zastępuje pomiaru. Zapytania GraphQL wysyłane metodą POST nie korzystają automatycznie z typowego cache CDN tak jak stabilne GET; jeśli publiczny ruch trafia bezpośrednio do WordPressa, rozważ persisted/safelisted queries albo warstwę cache świadomą GraphQL.
Nie wystawiaj w schemacie pól na zapas, bo WPGraphQL respektuje wiele uprawnień WordPressa, ale każde rozszerzenie może dodawać własne typy i resolvery. Przetestuj schemat jako użytkownik anonimowy, redaktor i konto podglądu, a introspekcję oraz IDE produkcyjne skonfiguruj zgodnie z przyjętym modelem zagrożeń.
Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.
WPGraphQL czy REST API — co wybrać do headless WordPress?
WPGraphQL daje typowany schemat, relacje i precyzyjny wybór pól, co pomaga przy złożonych modelach treści. REST jest prostszy operacyjnie, działa bez dodatkowej wtyczki i łatwiej korzysta z cache HTTP/CDN. GraphQL nie gwarantuje mniejszej ani szybszej odpowiedzi, bo nieprawidłowo ograniczone zapytanie może być bardziej kosztowne niż REST. Wybór zależy od modelu i infrastruktury.
Czy muszę utrzymywać dwa serwery?
Utrzymujesz dwa środowiska uruchomieniowe: WordPress z bazą oraz frontend Next.js, choć mogą działać u jednego dostawcy. WordPress nadal obsługuje GraphQL, redakcję, media i webhooki, więc jego wydajność, backupy, aktualizacje i bezpieczeństwo bezpośrednio wpływają na proces budowania aplikacji oraz świeżość frontendu. Tani hosting współdzielony nie zawsze wystarczy.
Jak obsłużyć podgląd szkicu (preview) z WordPressa?
Draft Mode w Next.js tylko omija statyczny cache, nie daje dostępu do prywatnych danych WordPressa. Potrzebujesz podpisanego URL-a podglądu, uwierzytelnionego zapytania WPGraphQL z kontem o minimalnych uprawnieniach oraz obsługi rewizji lub szkicu. Integrację można napisać samodzielnie albo użyć utrzymywanego rozwiązania zgodnego z wybranym mechanizmem uwierzytelniania.
Dlaczego next/image nie ładuje zdjęć z WordPressa?
Domyślnie next/image optymalizuje tylko obrazy z dozwolonych hostów, więc dodaj host swojego WordPressa do images.remotePatterns w next.config. W innym wypadku Next.js odrzuci zewnętrzne URL-e z biblioteki mediów.
Jak odświeżać front po publikacji w WordPressie?
Przez podpisany webhook reagujący na zmianę statusu wpisu. Endpoint w Next.js unieważnia tag kolekcji, tag wpisu oraz ścieżki nowego i starego sluga. Odfiltruj autosave i rewizje, waliduj ciało żądania, chroń się przed ponowieniem starego żądania i ponawiaj nieudane dostarczenia.
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.
Headless WordPress brzmi jak przepis na nowoczesną architekturę: WordPress do zarządzania treścią, Next.js do frontendu, lepsza wydajność i więcej kontroli. Problem w tym, że ten układ potrafi być świetnym wyborem albo kosztownym przerostem formy, zależnie od rodzaju projektu.
Maciej Sala
Founder StriveLab
Z jednej strony mamy WordPressa — weterana, który wciąż napędza ponad 40% globalnego internetu. Z drugiej Next.js, flagowy framework wśród nowoczesnych technologii opartych na Reakcie. Cel obu narzędzi jest ten sam, by wypuścić stronę w świat. Różnią się jednak diametralnie drogą do tego celu, a wybór między nimi to kwestia tego, co konkretny projekt ma osiągnąć i kto będzie go utrzymywał.
Maciej Sala
Founder StriveLab
WordPress to nie tylko PHP i szablony, ponieważ od wersji 4.7 ma wbudowane REST API , dzięki któremu może funkcjonować jako headless CMS . Backend w WordPressie, frontend w React lub Next.js to w wielu projektach bardzo rozsądne rozwiązanie.