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.jsReactWydajnośćSEO

Partial Prerendering (PPR) w Next.js — hybryda static + dynamic, która zmieni frontend

Partial Prerendering (PPR) w Next.js łączy szybkość statycznych stron z dynamiczną personalizacją. Poznaj jak działa, jak go włączyć i dlaczego zmienia podejście do renderingu.

OpublikujLinkedInFacebookWyślij
Autor
Maciej Sala
Opublikowano
10 kwietnia 2026 09:30
Czytanie
5 min czytania
Aktualizacja
25 maja 2026 10:55

Przez lata rendering w Next.js był wyborem zero-jedynkowym: albo cała strona statyczna i błyskawiczna, albo cała dynamiczna i spersonalizowana. Partial Prerendering zburzył tę granicę — statyczna powłoka leci z CDN w kilkadziesiąt milisekund, a dynamiczne fragmenty doczytują się w tle. W Next.js 16 ta idea dojrzała do stabilnych Cache Components. Pokazuję, jak to działa i jak zaprojektować pod to architekturę.

Czym jest Partial Prerendering?

Partial Prerendering (PPR) to model renderingu w Next.js, który pozwala na jednej stronie łączyć elementy statyczne (generowane w build time) z elementami dynamicznymi (renderowanymi na żądanie).

Artykuł w skrócie

  • Koniec wyboru „cała strona statyczna albo cała dynamiczna" — PPR pozwala łączyć oba na jednej stronie: statyczna powłoka z CDN + dynamiczne wyspy streamowane w tle.
  • Decyzja przenosi się z poziomu strony na poziom komponentu — statyczne fragmenty oznaczasz "use cache", request-time islands opakowujesz w <Suspense>.
  • W Next.js 16 to stabilne Cache Components — cacheComponents jako top-level config; wczesna flaga experimental.ppr została usunięta.
  • Idealny dla stron 80% statycznych z 20% personalizacji — e-commerce, strony produktowe; czysto statyczny blog PPR nie potrzebuje.
  • Co czyni komponent dynamicznym — użycie cookies(), headers(), searchParams albo fetch z no-store; takie fragmenty wymagają <Suspense>.

Do tej pory deweloperzy musieli wybierać: cała strona jest statyczna (SSG (Static Site Generation) to generowanie kompletnego HTML podczas budowania strony. Gotowe pliki serwujesz z CDN bez renderowania na żądanie — najszybszy i najtańszy model dla treści, która nie zmienia się przy każdej wizycie./ISR, czyli Incremental Static Regeneration, pozwala odświeżać strony statyczne w tle bez pełnego rebuildu — strona jest serwowana z cache, a Next.js regeneruje ją po upływie czasu revalidate.) albo cała jest dynamiczna (SSR, czyli Server-Side Rendering, to generowanie HTML na serwerze przy żądaniu — komponent client:only je pomija i renderuje się wyłącznie w przeglądarce.). PPR eliminuje ten wybór — statyczna powłoka strony jest serwowana natychmiast z CDN, czyli Content Delivery Network, to rozproszona sieć serwerów dostarczająca zasoby z węzła najbliższego użytkownikowi; CDN do obrazów dodatkowo transformuje je w locie., a dynamiczne „wyspy" Streaming to wysyłanie odpowiedzi HTML partiami, zanim wszystkie dane i komponenty będą gotowe. w tle.

W starszych wersjach Next.js PPR było pokazywane jako osobna, eksperymentalna funkcja. W aktualnych wersjach ten sam kierunek rozwija się pod nazwą Cache Components i opiera się na cacheComponents, dyrektywie "use cache" oraz Suspense.

To fundamentalna zmiana w architekturze frontendu. Zamiast decydować o modelu renderingu na poziomie strony — decydujesz na poziomie komponentu.

Jak PPR działa pod maską

PPR opiera się na trzech mechanizmach:

  1. Statyczna powłoka — layout, nagłówek, nawigacja, footer i wszelkie treści, które nie zależą od requestu użytkownika. Generowane w build time, serwowane z CDN.

  2. Dynamiczne wyspy (holes) — fragmenty strony opakowane w <Suspense>, które wymagają danych zależnych od requestu (cookies, headers, params).

  3. Streaming — dynamiczne wyspy są renderowane na serwerze przy każdym requeście i streamowane do przeglądarki, wypełniając „dziury" w statycznej powłoce.

Code
Request → CDN serwuje statyczną powłokę (< 50 ms)
       → Przeglądarka renderuje powłokę ze skeletonami
       → Serwer renderuje dynamiczne wyspy
       → Streaming wypełnia skeletony prawdziwą treścią

Włączenie PPR

W aktualnym podejściu włączasz ten model przez Cache Components:

Code
// next.config.ts
const nextConfig = {
  cacheComponents: true,
};
 
export default nextConfig;

Jeśli czytasz starsze materiały, zobaczysz jeszcze experimental.ppr = 'incremental' i export const experimental_ppr = true. To wcześniejszy etap tej samej idei. Dzisiaj praktycznym punktem odniesienia są cacheComponents, "use cache" i Suspense.

Oznaczanie strony jako PPR

Code
// app/product/[id]/page.tsx
 
import { Suspense } from 'react';
 
export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
 
  return (
    <main>
      {/* STATYCZNE — generowane w build time */}
      <Header />
      <Breadcrumbs product={id} />
 
      {/* STATYCZNE — dane produktu z cache */}
      <ProductInfo id={id} />
 
      {/* DYNAMICZNE — zależne od sesji użytkownika */}
      <Suspense fallback={<PriceSkeleton />}>
        <PersonalizedPrice id={id} />
      </Suspense>
 
      {/* DYNAMICZNE — zależne od cookies */}
      <Suspense fallback={<CartButtonSkeleton />}>
        <AddToCartButton id={id} />
      </Suspense>
 
      {/* STATYCZNE — opis, specyfikacja */}
      <ProductDescription id={id} />
 
      {/* DYNAMICZNE — real-time dane */}
      <Suspense fallback={<ReviewsSkeleton />}>
        <LiveReviews id={id} />
      </Suspense>
 
      {/* STATYCZNE */}
      <Footer />
    </main>
  );
}

W tym modelu nie oznaczasz już strony jedną flagą per-route. Zamiast tego oznaczasz statyczne fragmenty przez "use cache", a request-time islands opakowujesz w <Suspense>.

Co sprawia, że komponent jest dynamiczny?

Next.js automatycznie wykrywa, które komponenty są dynamiczne na podstawie użycia API zależnych od requestu:

Code
// DYNAMICZNY — czyta cookies
import { cookies } from 'next/headers';
 
async function PersonalizedPrice({ id }: { id: string }) {
  const cookieStore = await cookies();
  const userSegment = cookieStore.get('segment')?.value || 'standard';
 
  const price = await fetch(
    `https://api.example.com/pricing/${id}?segment=${userSegment}`,
    { cache: 'no-store' }
  );
  const data = await price.json();
 
  return <span className="text-2xl font-bold">{data.price} PLN</span>;
}
Code
// DYNAMICZNY — czyta headers
import { headers } from 'next/headers';
 
async function GeoContent() {
  const headersList = await headers();
  const country = headersList.get('x-country') || 'PL';
  // ...
}
Code
// STATYCZNY — nie używa cookies/headers, dane cachowane
import { cacheLife } from 'next/cache';
 
async function ProductInfo({ id }: { id: string }) {
  'use cache';
  cacheLife('hours');
 
  const product = await fetch(`https://api.example.com/products/${id}`, {
    cache: 'force-cache',
  });
  const data = await product.json();
 
  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.description}</p>
    </div>
  );
}

Reguła jest prosta: jeśli komponent używa cookies(), headers(), searchParams, connection() lub fetch z cache: 'no-store', traktuj go jako request-time island i opakuj w <Suspense>. Statyczne fragmenty oznaczaj "use cache" albo cachuj ich dane na poziomie funkcji i komponentów.

PPR w praktyce — strona e-commerce

Najlepszym przykładem wartości PPR jest strona produktowa w sklepie internetowym.

Bez PPR: cała strona jest SSR (bo potrzebujesz spersonalizowanej ceny i stanu koszyka). TTFB, czyli Time To First Byte, mierzy czas od żądania do otrzymania pierwszego bajtu odpowiedzi z serwera. wynosi 200–500 ms.

Z PPR: 90% strony (layout, opis produktu, specyfikacja, footer) serwowane natychmiast z CDN (< 50 ms). Tylko cena i przycisk „dodaj do koszyka" renderują się dynamicznie.

Code
export async function generateStaticParams() {
  return [{ id: 'nike-air-max-90' }, { id: 'adidas-superstar' }];
}
 
export default async function ProductPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const product = await getProduct(id); // Cachowane — statyczne
 
  return (
    <main className="max-w-4xl mx-auto">
      {/* === STATYCZNA POWŁOKA (CDN, < 50ms) === */}
      <nav>
        <Breadcrumbs items={[
          { label: 'Sklep', href: '/sklep' },
          { label: product.category, href: `/sklep/${product.categorySlug}` },
          { label: product.name },
        ]} />
      </nav>
 
      <div className="grid grid-cols-2 gap-8">
        <ProductGallery images={product.images} />
 
        <div>
          <h1 className="text-3xl font-bold">{product.name}</h1>
          <p className="text-gray-600 mt-2">{product.shortDescription}</p>
 
          {/* === DYNAMICZNA WYSPA: spersonalizowana cena === */}
          <Suspense
            fallback={
              <div className="mt-4 h-10 w-32 animate-pulse rounded bg-gray-200" />
            }
          >
            <DynamicPricing productId={id} />
          </Suspense>
 
          {/* === DYNAMICZNA WYSPA: koszyk === */}
          <Suspense
            fallback={
              <div className="mt-4 h-12 w-full animate-pulse rounded bg-gray-200" />
            }
          >
            <AddToCart productId={id} />
          </Suspense>
 
          {/* === STATYCZNE: specyfikacja === */}
          <ProductSpecs specs={product.specifications} />
        </div>
      </div>
 
      {/* === STATYCZNE: opis === */}
      <section className="mt-12 prose max-w-none">
        <h2>Opis produktu</h2>
        <div dangerouslySetInnerHTML={{ __html: product.fullDescription }} />
      </section>
 
      {/* === DYNAMICZNA WYSPA: opinie === */}
      <Suspense fallback={<ReviewsSkeleton />}>
        <ProductReviews productId={id} />
      </Suspense>
 
      {/* === STATYCZNE: powiązane produkty === */}
      <RelatedProducts categoryId={product.categoryId} />
    </main>
  );
}

PPR a SEO

PPR jest korzystny dla SEO z kilku powodów:

Szybszy TTFB

Statyczna powłoka dociera do przeglądarki (i crawlera) natychmiast. Google wyraźnie preferuje strony z niskim TTFB w Core Web Vitals.

Pełna treść dla crawlerów

W praktyce crawler najpewniej zobaczy statyczną powłokę bardzo szybko, a renderowanie dynamicznych wysp będzie zależeć od czasu odpowiedzi i procesu renderowania. Dlatego krytyczne elementy SEO, takie jak H1, lead, opis produktu i główna treść, najlepiej utrzymywać w statycznej shell, a nie odkładać ich do późno streamowanych komponentów.

Lepszy LCP

Największy element strony (hero image, tytuł produktu) jest częścią statycznej powłoki — pojawia się natychmiast, bez czekania na dynamiczne dane. Bezpośrednio przekłada się to na LCP, czyli Largest Contentful Paint, mierzy czas do wyrenderowania największego widocznego elementu — oznaczenie go preload przyspiesza jego załadowanie..

Ograniczenia PPR

Ewolucja API — od eksperymentu do stabilności

To obszar, który szybko dojrzewał, więc łatwo trafić na nieaktualne materiały. Wczesna flaga experimental.ppr została usunięta na rzecz Cache Components. W Next.js 16 cacheComponents to top-level config (już nie experimental), a dyrektywy "use cache", cacheLife i cacheTag są stabilne od 16.2 (zniknął prefiks unstable_). Jeśli czytasz tutorial z experimental_ppr = true albo unstable_cache, to treść sprzed stabilizacji — punktem odniesienia jest dziś cacheComponents.

Wymaga Suspense boundaries

Każdy dynamiczny fragment musi być opakowany w <Suspense>. Jeśli zapomnisz — cała strona stanie się dynamiczna, tracąc korzyści PPR.

Wymaga platformy z streaming

PPR potrzebuje hostingu obsługującego HTTP streaming. Vercel, AWS Lambda z streaming, self-hosted Node.js — tak. Statyczny export — nie.

Debugowanie

Ustalenie, które komponenty są statyczne, a które dynamiczne, może być nieoczywiste. Next.js dev mode pokazuje wskaźniki (Static Indicator), ale w złożonych drzewach komponentów zależności bywają trudne do wyśledzenia.

Code
# Build output pokaże, które strony używają PPR
next build
 
# ◐ — PPR (statyczna powłoka + dynamiczne wyspy)
# ● — SSG (w pełni statyczna)
# ƒ — SSR (w pełni dynamiczna)

PPR vs inne modele renderingu

ModelGenerowaniePersonalizacjaTTFBUżycie
SSGBuild timeBrakNajszybszyBlogi, docs, landing pages
ISRBuild + rewalidacjaBrakSzybkiProdukty, artykuły z CMS
SSRKażdy requestPełnaWolnyDashboardy, panele admin
PPRHybrydaCzęściowaSzybki (powłoka z CDN)E-commerce, strony z personalizacją

PPR nie zastępuje SSG ani ISR — uzupełnia je. Strona czysto statyczna (blog, dokumentacja) nie potrzebuje PPR. Strona, która jest w 80% statyczna, ale ma 20% dynamicznej personalizacji — to idealny kandydat.

Jak przygotować się na PPR

Nawet jeśli nie włączysz PPR dzisiaj, możesz przygotować architekturę:

  1. Oddzielaj komponenty statyczne od dynamicznych — nie mieszaj cookies() i statycznych danych w jednym komponencie
  2. Używaj Suspense — każdy komponent z danymi powinien mieć boundary
  3. Cachuj agresywnie — dane, które mogą być statyczne, oznaczaj cache: 'force-cache'
  4. Przenoś dynamiczne API do dedykowanych komponentów — cookies() i headers() tylko w liściach drzewa komponentów

Te praktyki poprawią wydajność już teraz, a gdy PPR dojrzeje — migracja będzie bezbolesna.

Werdykt Labu

Partial Prerendering to koniec ery „strona jest albo statyczna, albo dynamiczna". PPR pozwala budować strony szybkie jak SSG i spersonalizowane jak SSR — jednocześnie: statyczna powłoka z CDN plus dynamiczne wyspy streamowane w tle. Decyzja o renderowaniu przenosi się z poziomu strony na poziom komponentu.

W Next.js 16 ta idea dojrzała do stabilnych Cache Components — cacheComponents jako top-level config, "use cache" bez prefiksu unstable_. Nawet jeśli nie włączasz tego dziś, projektuj architekturę pod podział na cached UI i request-time islands: separacja static/dynamic, Suspense wokół danych zależnych od żądania i brak mieszania cookies() z treścią współdzieloną. Te praktyki poprawiają wydajność już teraz, a migrację robią bezbolesną.

Jeśli budujesz aplikację w Next.js, która ma być szybka i spersonalizowana naraz — odezwij się.

  • Czym jest Partial Prerendering?1 min
  • Jak PPR działa pod maską1 min
  • Włączenie PPR1 min
  • PPR w praktyce — strona e-commerce1 min
  • PPR a SEO1 min
  • Ograniczenia PPR1 min
  • PPR vs inne modele renderingu1 min
  • Jak przygotować się na PPR1 min
  • Werdykt Labu1 min

Często zadawane pytania

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

Status Cache Components, dyrektywy use cache i model renderingu zweryfikowano na podstawie oficjalnej dokumentacji Next.js:

Next.js 16 release notes, Next.js docs: cacheComponents, Next.js docs: Caching, Next.js docs: use cache, React docs: Suspense.

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