StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Doradztwo produktowe

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

StriveLab
Strony internetowe
Usługi
RealizacjeO mnieBlogPorozmawiajmy
PL
EN

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Doradztwo produktowe

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

Astro

Ultraszybkie projekty, łączące lekkość ze skalowalnością.

Next.js

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.

React

Połączenie intuicyjności z wydajnością, które zapewnia bezproblemową skalowalność kodu.

SEO & Performance

Audyt techniczny i optymalizacja pod kątem SEO i GEO.

Automatyzacja AI

Bezpieczne automatyzacje procesów i agenci AI w n8n, Make i Claude.

QA & Automation

Testy automatyczne komponentów i E2E w Cypress.

Doradztwo produktowe

Połączenie perspektywy produktu, developera i marketingu w jednym miejscu

RealizacjeO mnieBlog
Porozmawiajmy
PL
EN

Nowoczesne strony internetowe dla firm, które myślą odważnie.

Przewiń do góry

Nazwa

StriveLab Maciej Sala

NIP

6772218995

REGON

524008527

E-mail

contact@strivelab.pl

Usługi główne
  • Tworzenie stron internetowych
  • Strony internetowe Next.js
  • Strony internetowe Astro
  • Strony internetowe React
Inne usługi
  • Usługi
  • SEO & Performance Sprint
  • QA & Stabilizacja
  • Konsultacje Product / Delivery
  • Automatyzacja Procesów AI
  • Aplikacje webowe Next.js
  • Współpraca ciągła
Strony
  • O mnie
  • Usługi
  • Realizacje
  • Blog

© 2026 StriveLab.pl

Polityka prywatności
Next.jsWordPressBackend

WPGraphQL + Next.js App Router — implementacja headless WordPress krok po kroku

Jak technicznie wdrożyć headless WordPress z WPGraphQL i Next.js App Router? Klient GraphQL, zapytania, ISR, webhooks, next/image i preview bez mieszania z decyzją biznesową.

OpublikujLinkedInFacebookWyślij
Autor
Maciej Sala
Opublikowano
11 kwietnia 2026 10:10
Czytanie
3 min czytania
Aktualizacja
25 maja 2026 10:55

Decyzja zapadła: headless WordPress z Next.js. Klient zostaje przy znanym edytorze, Ty dostajesz wydajny frontend. Teraz trzeba to tylko spiąć — a między „trzeba spiąć" a „działa na produkcji" leży kilka konkretnych kroków: klient GraphQL, zapytania, ISR, webhooki i obrazy z WordPressa w next/image. Ten tutorial przeprowadza przez nie po kolei. Jeśli decyzja jeszcze nie zapadła, zacznij od Headless WordPress — kiedy ma sens, a kiedy nie.

Zakres: implementacja WPGraphQL + Next.js App Router

Ten wpis jest technicznym tutorialem wdrożeniowym: jak połączyć WordPress, WPGraphQL to wtyczka WordPressa udostępniająca treść przez API GraphQL — pobierasz dokładnie te pola, których potrzebujesz, w jednym żądaniu. i Next.js App Router w działającą architekturę. Headless WordPress to WordPress używany wyłącznie jako CMS, z Next.js jako frontendem.

Artykuł w skrócie

  • WPGraphQL eksponuje treść WordPressa przez GraphQL — pobierasz dokładnie potrzebne pola w jednym żądaniu, mniejsze payloady niż REST.
  • Cztery filary implementacji: cienki klient GraphQL (fetchGraphQL), typowane zapytania, ISR z revalidate, generateStaticParams do pre-renderingu.
  • Webhook z WordPressa domyka on-demand ISR — save_post → POST do /api/revalidate → unieważnienie cache; zawsze weryfikuj sekret.
  • next/image wymaga remotePatterns z hostem WordPressa, żeby optymalizować zdjęcia z biblioteki mediów.
  • Podgląd draftów przez WPGraphQL Preview + Next.js Draft Mode; WPGraphQL > REST dla headless dzięki precyzji zapytań.

WordPress udostępnia treści przez API (REST lub GraphQL), a Next.js pobiera je, renderuje i serwuje użytkownikowi. Efekt: znany edytor WordPress dla klienta + wydajność i SEO Next.js na froncie.

Kiedy headless WordPress ma sens

  • Istniejący WordPress z setkami artykułów — klient nie chce migrować treści, ale chce szybszy frontend
  • Zespół redakcyjny zna WordPress — nie chcą uczyć się Sanity/Strapi
  • Rozbudowane ACF (Advanced Custom Fields) — custom pola, flexible content, grupy pól
  • WooCommerce — headless checkout z Next.js, ale zarządzanie produktami w WordPress

Kiedy lepiej wybrać dedykowany headless CMS

  • Nowy projekt od zera — Sanity/Strapi mają lepszy DX, nie ciągniesz ze sobą PHP
  • Nie potrzebujesz ekosystemu pluginów WP — mniej zależności = mniej problemów
  • Jeden deweloper — utrzymanie WordPress + Next.js to dwa systemy do zarządzania

Setup — WPGraphQL

Code
# W WordPress — zainstaluj pluginy:
# 1. WPGraphQL (wp-graphql)
# 2. WPGraphQL for ACF (opcjonalnie, jeśli używasz ACF)

Po instalacji WPGraphQL: endpoint GraphQL to język zapytań do API, w którym klient precyzyjnie określa, jakie pola chce pobrać — zamiast sztywnych endpointów REST zwracających całe obiekty. dostępny na https://twoj-wordpress.pl/graphql.

Klient GraphQL w Next.js

Code
// lib/wordpress.ts
const WORDPRESS_API_URL = process.env.WORDPRESS_API_URL!;
 
interface GraphQLResponse<T> {
  data: T;
  errors?: { message: string }[];
}
 
export async function fetchGraphQL<T>(
  query: string,
  variables?: Record<string, unknown>
): Promise<T> {
  const res = await fetch(WORDPRESS_API_URL, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      ...(process.env.WORDPRESS_AUTH_TOKEN && {
        Authorization: `Bearer ${process.env.WORDPRESS_AUTH_TOKEN}`,
      }),
    },
    body: JSON.stringify({ query, variables }),
    next: { revalidate: 3600 }, // ISR — odśwież co godzinę
  });
 
  const json: GraphQLResponse<T> = await res.json();
 
  if (json.errors) {
    throw new Error(json.errors.map((e) => e.message).join(', '));
  }
 
  return json.data;
}

Zapytania GraphQL

Code
// lib/queries/posts.ts
import { fetchGraphQL } from '../wordpress';
 
interface WPPost {
  id: string;
  title: string;
  slug: string;
  excerpt: string;
  date: string;
  content: string;
  featuredImage: {
    node: {
      sourceUrl: string;
      altText: string;
    };
  } | null;
  categories: {
    nodes: { name: string; slug: string }[];
  };
  seo: {
    title: string;
    metaDesc: string;
    opengraphImage: { sourceUrl: string } | null;
  };
}
 
export async function getPosts(first = 20): Promise<WPPost[]> {
  const data = await fetchGraphQL<{ posts: { nodes: WPPost[] } }>(`
    query GetPosts($first: Int!) {
      posts(first: $first, where: { status: PUBLISH }) {
        nodes {
          id
          title
          slug
          excerpt
          date
          featuredImage {
            node {
              sourceUrl
              altText
            }
          }
          categories {
            nodes {
              name
              slug
            }
          }
        }
      }
    }
  `, { first });
 
  return data.posts.nodes;
}
 
export async function getPostBySlug(slug: string): Promise<WPPost | null> {
  const data = await fetchGraphQL<{ post: WPPost | null }>(`
    query GetPost($slug: ID!) {
      post(id: $slug, idType: SLUG) {
        id
        title
        slug
        content
        date
        featuredImage {
          node {
            sourceUrl
            altText
          }
        }
        categories {
          nodes {
            name
            slug
          }
        }
      }
    }
  `, { slug });
 
  return data.post;
}
 
export async function getAllPostSlugs(): Promise<string[]> {
  const data = await fetchGraphQL<{ posts: { nodes: { slug: string }[] } }>(`
    query GetAllSlugs {
      posts(first: 1000, where: { status: PUBLISH }) {
        nodes {
          slug
        }
      }
    }
  `);
 
  return data.posts.nodes.map((p) => p.slug);
}

Strony w Next.js

Code
// app/blog/page.tsx
import { getPosts } from '@/lib/queries/posts';
import Image from 'next/image';
import Link from 'next/link';
 
export const revalidate = 3600;
 
export default async function BlogPage() {
  const posts = await getPosts();
 
  return (
    <main className="max-w-4xl mx-auto py-12">
      <h1 className="text-3xl font-bold mb-8">Blog</h1>
      <div className="space-y-8">
        {posts.map((post) => (
          <Link key={post.id} href={`/blog/${post.slug}`} className="block group">
            <article className="flex gap-6">
              {post.featuredImage && (
                <Image
                  src={post.featuredImage.node.sourceUrl}
                  alt={post.featuredImage.node.altText || post.title}
                  width={300}
                  height={200}
                  className="rounded-lg object-cover"
                />
              )}
              <div>
                <h2 className="text-xl font-semibold group-hover:text-blue-600">
                  {post.title}
                </h2>
                <div
                  className="text-gray-600 mt-2 line-clamp-2"
                  dangerouslySetInnerHTML={{ __html: post.excerpt }}
                />
                <time className="text-sm text-gray-400 mt-2 block">
                  {new Date(post.date).toLocaleDateString('pl-PL')}
                </time>
              </div>
            </article>
          </Link>
        ))}
      </div>
    </main>
  );
}
Code
// app/blog/[slug]/page.tsx
import { getPostBySlug, getAllPostSlugs } from '@/lib/queries/posts';
import { notFound } from 'next/navigation';
import type { Metadata } from 'next';
 
export const revalidate = 3600;
 
export async function generateStaticParams() {
  const slugs = await getAllPostSlugs();
  return slugs.map((slug) => ({ slug }));
}
 
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPostBySlug(slug);
  if (!post) return {};
 
  return {
    title: post.title,
    description: post.excerpt?.replace(/<[^>]*>/g, '').slice(0, 160),
  };
}
 
export default async function BlogPost({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const post = await getPostBySlug(slug);
  if (!post) notFound();
 
  return (
    <article className="max-w-3xl mx-auto py-12 prose prose-lg">
      <h1>{post.title}</h1>
      <time className="text-gray-400">{new Date(post.date).toLocaleDateString('pl-PL')}</time>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

On-demand ISR z WordPress webhooks

Code
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
import { NextResponse } from 'next/server';
 
export async function POST(req: Request) {
  const secret = req.headers.get('x-webhook-secret');
  if (secret !== process.env.REVALIDATION_SECRET) {
    return NextResponse.json({ error: 'Invalid secret' }, { status: 401 });
  }
 
  const body = await req.json();
 
  if (body.post_type === 'post') {
    revalidatePath('/blog');
    if (body.post_name) revalidatePath(`/blog/${body.post_name}`);
  }
 
  return NextResponse.json({ revalidated: true });
}

W WordPress — plugin WP Webhooks lub custom save_post hook wysyłający POST do /api/revalidate przy każdej publikacji.

Konfiguracja next/image dla WordPress

Code
// next.config.ts
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'twoj-wordpress.pl',
      },
      {
        protocol: 'https',
        hostname: '**.wp.com', // Jeśli używasz WordPress.com CDN
      },
    ],
  },
};

Werdykt Labu

Headless WordPress z WPGraphQL + Next.js to sprawdzony stack, gdy klient zna WordPress i ma istniejące treści. WPGraphQL eksponuje dane przez precyzyjne zapytania, Next.js pobiera je z ISR i renderuje z pełnym SEO. Cała implementacja stoi na czterech filarach: cienki klient GraphQL, typowane zapytania, generateStaticParams do pre-renderingu i webhook do on-demand revalidation.

Dwie rzeczy, które łatwo przeoczyć: next/image nie zadziała ze zdjęciami z WordPressa bez remotePatterns, a endpoint rewalidacji bez sekretu jest otwartym wektorem ataku. Reszta to mechanika — a gdy działa, klient pisze w znanym edytorze, użytkownik dostaje szybki, dobrze zaindeksowany front.

Jeśli masz WordPressa z latami treści i chcesz front w Next.js, który nie każe redakcji niczego się uczyć — odezwij się.

  • Zakres: implementacja WPGraphQL + Next.js App Router1 min
  • Kiedy headless WordPress ma sens1 min
  • Kiedy lepiej wybrać dedykowany headless CMS1 min
  • Setup — WPGraphQL1 min
  • On-demand ISR z WordPress webhooks1 min
  • Konfiguracja next/image dla WordPress1 min
  • Werdykt Labu1 min

Często zadawane pytania

Źródła i dokumentacjaZweryfikowano: 30 maja 2026

API WPGraphQL, integrację z Next.js i Draft Mode zweryfikowano na podstawie oficjalnej dokumentacji:

WPGraphQL docs, Next.js docs: Fetching Data, Next.js docs: draftMode, Next.js docs: Image (remotePatterns).

Maciej Sala

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 rozwija własne projekty side-projecty.

Moje artykułyWięcej o mnie

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną

Biblioteka wiedzy

Czytaj dalej

Zobacz więcej wpisów
Cursor czy Antigravity? Co wybrać do kodowania z AI
Cursor czy Antigravity? Co wybrać do kodowania z AI

Cursor czy Antigravity w 2026? Porównanie dwóch filozofii kodowania z AI — pilot kontra autonomiczni agenci. Modele, ceny, limity, stabilność i realna przydatność we frontendzie.

Maciej Sala

Maciej Sala

Founder Strivelab

1 czerwca 2026
Audyt SEO to nie lista TODO — dlaczego zalecenia techniczne muszą zamieniać się w PR-y
Audyt SEO to nie lista TODO — dlaczego zalecenia techniczne muszą zamieniać się w PR-y

Większość audytów SEO kończy się jako PDF, którego nikt nie wdraża. Pokazuję, dlaczego techniczna optymalizacja działa dopiero, gdy zalecenia zamieniają się w pull requesty, i jak zorganizować ten proces.

Maciej Sala

Maciej Sala

Founder Strivelab

30 maja 2026
WordPress, Astro czy Next.js? Matryca decyzyjna, zanim ruszysz z migracją
WordPress, Astro czy Next.js? Matryca decyzyjna, zanim ruszysz z migracją

Zanim zaczniesz migrację, musisz wiedzieć dokąd. Matryca pięciu zmiennych (rozmiar serwisu, wydajność, e-commerce, model edycji treści, częstotliwość zmian) pokazuje, czy Twój następny stack to Astro, Next.js, czy nadal WordPress — zanim wydasz złotówkę na przepisywanie strony.

Maciej Sala

Maciej Sala

Founder Strivelab

29 maja 2026