Next.js 16 Cache Components és Partial Prerendering: A gyorsítótárazás új korszaka

Ismerd meg a Next.js 16 Cache Components és PPR rendszerét a gyakorlatban: use cache direktíva, cacheLife, cacheTag, updateTag és revalidateTag használata valós kódpéldákkal.

Bevezetés: Miért változtatja meg mindent a Cache Components?

Ha valaha is küzdöttél a Next.js App Router gyorsítótárazási rendszerével — és őszintén, ki ne tette volna —, akkor pontosan tudod, miről beszélek. A korábbi verziókban a gyorsítótárazás implicit és kiszámíthatatlan volt: a fetch hívások automatikusan cache-elődtek, a route-ok maguktól statikussá váltak, és sokszor fogalmad sem volt, miért nem frissül az oldal. A fejlesztői frusztráció akkora lett, hogy a Next.js csapat végül teljesen újragondolta az egész megközelítést.

Na és itt jön a képbe a Next.js 16.

A 2025 októberében megjelent verzió gyökeresen más utat választ. A Cache Components és a Partial Prerendering (PPR) együtt alkotja azt az architektúrát, amivel végre búcsút inthetsz a „minden vagy semmi" renderelési döntéseknek. Mostantól egyetlen oldalon belül keverheted a statikus, gyorsítótárazott és dinamikus tartalmat — mégpedig explicit, átlátható módon.

Ebben az útmutatóban végigmegyünk a Cache Components minden fontos részletén: a "use cache" direktívától a cacheLife és cacheTag API-kon át az updateTag és revalidateTag frissített működéséig. Ha már olvastad az adatlekérdezésről és a Server Actions-ről szóló korábbi cikkeinket, ez itt a kirakós utolsó darabja — az a réteg, ami mindent összeköt.

A régi rendszer problémái: Miért kellett változtatni?

Az implicit gyorsítótárazás csapdái

A Next.js 15-ben és korábbi verziókban a gyorsítótárazás alapértelmezetten be volt kapcsolva. Első hallásra remekül hangzik — gyors oldalak, minimális szerver terhelés. A gyakorlatban viszont rengeteg fejfájást okozott:

  • Kiszámíthatatlan viselkedés — Nem volt mindig egyértelmű, hogy egy adott fetch hívás cache-elődik-e vagy sem.
  • Minden vagy semmi renderelés — Elég volt egyetlen komponens, ami dinamikus adatot igényelt (mondjuk egy személyre szabott üdvözlés a fejlécben), és az egész oldal dinamikussá vált.
  • Nehéz hibakeresés — A fejlesztők gyakran nem tudták eldönteni, hogy a cache-ből vagy friss adatból kapják-e az eredményt. Ez nem vicc.
  • Konfiguráció-labirintus — A force-dynamic, force-static, revalidate, fetchCache és társaik kombinálása egyre bonyolultabb lett.

A Next.js 16 válasza: Explicit gyorsítótárazás

A Next.js 16 megfordítja az alapértelmezést: minden dinamikus, hacsak nem mondod meg kifejezetten, hogy legyen gyorsítótárazva. Ez sokkal kiszámíthatóbb viselkedést eredményez. A "use cache" direktíva révén pontosan te döntöd el, mit és meddig cache-elsz — nem a framework találgatja ki helyetted.

A Cache Components bekapcsolása és alapjai

Konfiguráció

A Cache Components használatba vételéhez egyetlen konfigurációs sor kell a next.config.ts fájlodba:

// next.config.ts
import type { NextConfig } from 'next';

const nextConfig: NextConfig = {
  cacheComponents: true,
};

export default nextConfig;

Ennyi. Ez a flag egyszerre engedélyezi a Cache Components-et és a Partial Prerendering-et (PPR). A korábbi experimental.ppr és experimental.dynamicIO flageket eltávolították — mostantól ez az egyetlen beállítás, amire szükséged van.

Hogyan működik a renderelés?

Amikor a cacheComponents engedélyezve van, a Next.js a következőképpen dolgozza fel az oldalad:

  1. Build időben — Végigmegy a komponens fán. Ami nem igényel hálózati kérést vagy futásidejű adatot, az automatikusan bekerül a statikus héjba (static shell).
  2. Dinamikus tartalom kezelése — Ha egy komponens hálózati kérést indít vagy futásidejű adatot használ, két opciód van:
    • Becsomagolod <Suspense>-be → Streaming: a fallback UI kerül a statikus héjba, a tényleges tartalom kérésidőben érkezik.
    • Ellátod a "use cache" direktívával → A cache-elt eredmény bekerül a statikus héjba.
  3. Kiszolgálás — A felhasználó azonnal megkapja a statikus héjat, a dinamikus részek pedig stream-elődnek, ahogy elkészülnek.

Ha egyik módszert sem alkalmazod egy dinamikus komponensre, a Next.js build időben hibát dob: „Uncached data was accessed outside of <Suspense>". Ez szándékos — a rendszer rákényszerít, hogy explicit döntést hozz. Személy szerint szeretem ezt a megközelítést, mert végre nem kell találgatni.

A "use cache" direktíva részletesen

Alapvető használat

A "use cache" direktíva aszinkron függvények és komponensek visszatérési értékét cache-eli. Három szinten alkalmazhatod:

1. Fájl szinten — az egész fájl cache-elődik:

// app/products/page.tsx
'use cache';

import { cacheLife } from 'next/cache';

export default async function ProductsPage() {
  cacheLife('hours');

  const products = await db.query('SELECT * FROM products');

  return (
    <div>
      <h1>Termékeink</h1>
      <ul>
        {products.map(p => (
          <li key={p.id}>{p.name} — {p.price} Ft</li>
        ))}
      </ul>
    </div>
  );
}

2. Komponens szinten — egyedi komponens cache-elése:

// components/BlogPosts.tsx
import { cacheLife } from 'next/cache';

export default async function BlogPosts() {
  'use cache';
  cacheLife('days');

  const posts = await fetch('https://api.example.com/posts');
  const data = await posts.json();

  return (
    <section>
      <h2>Legfrissebb bejegyzések</h2>
      {data.map((post: any) => (
        <article key={post.id}>
          <h3>{post.title}</h3>
          <p>{post.excerpt}</p>
        </article>
      ))}
    </section>
  );
}

3. Függvény szinten — adatlekérdező függvény cache-elése:

// lib/data.ts
import { cacheLife, cacheTag } from 'next/cache';

export async function getProducts(categoryId: string) {
  'use cache';
  cacheLife('hours');
  cacheTag('products', `category-${categoryId}`);

  const products = await db
    .select()
    .from(productsTable)
    .where(eq(productsTable.categoryId, categoryId));

  return products;
}

A legtöbb projektben a 3. opciót fogod a leggyakrabban használni — adatlekérdező függvényekre rakni a cache-t a legtisztább megoldás.

Automatikus cache kulcsok

Az egyik legelegánsabb része az egésznek, hogy a cache kulcsokat automatikusan generálja a fordító. Az argumentumok és a szülő hatókörből bezárt (closure) értékek mind részévé válnak a cache kulcsnak. Szóval különböző bemenetekhez automatikusan különböző cache bejegyzések tartoznak — személyre szabott vagy paraméterezett cache-elt tartalmat is készíthetsz anélkül, hogy manuálisan kellene kulcsokat kezelned.

// A categoryId automatikusan a cache kulcs része lesz
export async function getProducts(categoryId: string) {
  'use cache';
  // categoryId = 'electronics' → külön cache bejegyzés
  // categoryId = 'clothing' → külön cache bejegyzés
  const products = await fetchProducts(categoryId);
  return products;
}

A cacheLife API: Mennyi ideig éljen a gyorsítótár?

Beépített profilok

A cacheLife függvény meghatározza, mennyi ideig maradjon érvényes a cache-elt tartalom. A Next.js 16 számos beépített profilt kínál, és ezek lefedik a legtöbb valós use case-t:

import { cacheLife } from 'next/cache';

// Rövid élettartam — árak, készletadatok
cacheLife('minutes');

// Közepes élettartam — napi néhányszor frissülő tartalom
cacheLife('hours');

// Hosszú élettartam — blogbejegyzések, stabil tartalom
cacheLife('days');

// Nagyon hosszú élettartam — ritkán változó tartalom
cacheLife('weeks');

// Maximális élettartam — háttérben revalidálódik
cacheLife('max');

Egyedi konfiguráció

Ha a beépített profilok nem felelnek meg az igényeidnek (és előbb-utóbb biztos lesz ilyen eset), egyedi konfigurációt is megadhatsz három időzítési paraméterrel:

export default async function DashboardStats() {
  'use cache';
  cacheLife({
    stale: 3600,      // 1 óra — ennyi ideig használja a kliens ellenőrzés nélkül
    revalidate: 7200,  // 2 óra — ezután háttérben frissül
    expire: 86400,     // 1 nap — ezután teljesen lejár
  });

  const stats = await db.query('SELECT * FROM dashboard_stats');
  return <StatsGrid stats={stats} />;
}

A három paraméter együttműködése röviden:

  • stale — Ennyi ideig a kliens a helyi cache-ből szolgálja ki a tartalmat, szerver felé való ellenőrzés nélkül.
  • revalidate — Ennyi idő elteltével a következő kérés háttérben frissítést indít (ez a jól ismert stale-while-revalidate szemantika).
  • expire — Ennyi idő elteltével a cache bejegyzés teljesen törlődik, mintha sosem létezett volna.

Feltételes cache élettartam

Az egyik leggyakoribb (és szerintem leghasznosabb) minta a feltételes cache élettartam — például vázlat tartalom rövidebb, publikált tartalom hosszabb ideig cache-elődik:

import { cacheLife, cacheTag } from 'next/cache';

async function getPostContent(slug: string) {
  'use cache';
  const post = await fetchPost(slug);
  cacheTag(`post-${slug}`);

  if (!post) {
    // Még nem létező tartalom — rövid cache az adatbázis terhelés csökkentésére
    cacheLife('minutes');
    return null;
  }

  if (post.status === 'draft') {
    // Vázlat — gyakrabban frissül
    cacheLife('minutes');
    return post.data;
  }

  // Publikált tartalom — hosszabb cache
  cacheLife('days');
  return post.data;
}

Partial Prerendering (PPR): Statikus és dinamikus tartalom egy oldalon

A probléma, amit a PPR megold

Képzelj el egy termékoldalt. Az elrendezés, a képek és a leírás alig változik, de a készletszám, az ajánlások és a felhasználó-specifikus műveletek igen. A Next.js 16 előtt gyakorlatilag két választásod volt:

  1. Az egész oldal statikus → gyors, de a dinamikus részek nem frissülnek.
  2. Az egész oldal dinamikus → friss adatok, de lassabb betöltés minden egyes kérésnél.

A PPR megszünteti ezt a dilemmát. A Next.js előre rendereli a statikus héjat (navigáció, elrendezés, cache-elt tartalom), a dinamikus részeket pedig <Suspense> boundary-kkel jelöli meg, amelyek kérésidőben stream-elődnek. Végre nem kell választanod — megkaphatod mindkettőt.

Teljes példa: Statikus, cache-elt és dinamikus tartalom együtt

Lássuk a gyakorlatban, hogyan néz ki, amikor a három réteg együttműködik:

// app/shop/page.tsx
import { Suspense } from 'react';
import { cookies } from 'next/headers';
import { cacheLife, cacheTag } from 'next/cache';
import Link from 'next/link';

export default function ShopPage() {
  return (
    <>
      {/* Statikus tartalom — automatikusan a héj része */}
      <header>
        <h1>Webshop</h1>
        <nav>
          <Link href="/">Főoldal</Link>
          <Link href="/kategoriak">Kategóriák</Link>
        </nav>
      </header>

      {/* Cache-elt dinamikus tartalom — a héjba kerül */}
      <ProductCatalog />

      {/* Futásidejű dinamikus tartalom — stream-elődik */}
      <Suspense fallback={<p>Kosár betöltése...</p>}>
        <UserCart />
      </Suspense>
    </>
  );
}

// Mindenki ugyanazt a termékkatalógust látja (óránként frissül)
async function ProductCatalog() {
  'use cache';
  cacheLife('hours');
  cacheTag('products');

  const products = await db.query('SELECT * FROM products WHERE active = true');

  return (
    <section>
      <h2>Termékeink</h2>
      <div className="grid grid-cols-3 gap-4">
        {products.map((product: any) => (
          <div key={product.id} className="product-card">
            <h3>{product.name}</h3>
            <p>{product.price} Ft</p>
          </div>
        ))}
      </div>
    </section>
  );
}

// Személyre szabott kosár — felhasználónként más
async function UserCart() {
  const sessionId = (await cookies()).get('session')?.value;
  if (!sessionId) return <p>Jelentkezz be a kosár megtekintéséhez.</p>;

  const cart = await fetchCart(sessionId);

  return (
    <aside>
      <h2>Kosarad ({cart.items.length} tétel)</h2>
      <p>Összesen: {cart.total} Ft</p>
    </aside>
  );
}

Ebben a példában három réteg működik együtt, és pont ez a PPR lényege:

  • A fejléc és navigáció tisztán statikus — build időben renderelődik, villámgyors.
  • A termékkatalógus cache-elődik "use cache"-sel, óránként frissül, és szintén a statikus héj része.
  • A felhasználói kosár <Suspense>-be van csomagolva — kérésidőben stream-elődik, mert cookie-t olvas.

A cacheTag és a revalidálási stratégiák

Cache bejegyzések címkézése

A cacheTag függvénnyel címkéket rendelhetsz a cache bejegyzéseidhez, amelyeket később szelektíven érvényteleníthetsz. Egy bejegyzéshez legfeljebb 128 címkét adhatsz, és minden címke maximum 256 karakter hosszú lehet. (A gyakorlatban persze ennél jóval kevesebb is bőven elég.)

import { cacheTag } from 'next/cache';

export async function getProduct(productId: string) {
  'use cache';
  cacheTag('products', `product-${productId}`);

  const product = await db.query(
    'SELECT * FROM products WHERE id = ?',
    [productId]
  );

  return product;
}

updateTag — Azonnali frissítés Server Actions-ben

Az updateTag egy vadonatúj, kifejezetten Server Actions számára tervezett API, amely read-your-writes szemantikát biztosít. Magyarul: a felhasználó azonnal látja a saját változtatásait — nem kell várnia a háttérben futó revalidálásra.

'use server';

import { updateTag } from 'next/cache';

export async function updateProductPrice(productId: string, newPrice: number) {
  // Adatbázis frissítése
  await db.query(
    'UPDATE products SET price = ? WHERE id = ?',
    [newPrice, productId]
  );

  // Cache azonnali érvénytelenítése és frissítése
  updateTag(`product-${productId}`);
  updateTag('products');
}

Az updateTag és a Server Actions tökéletesen kiegészítik egymást: a Server Action végzi a mutációt, az updateTag pedig biztosítja, hogy a UI azonnal tükrözze a változást. Ha olvastad a Server Actions-ről szóló korábbi cikkünket, ez a hiányzó puzzle-darab.

revalidateTag — Háttérben futó frissítés

A revalidateTag a Next.js 16-ban frissített aláírást kapott. Mostantól kötelező egy cacheLife profil második argumentumként, ami a stale-while-revalidate (SWR) viselkedést szabályozza:

import { revalidateTag } from 'next/cache';

// ✅ Ajánlott — SWR viselkedéssel
revalidateTag('blog-posts', 'max');
revalidateTag('news-feed', 'hours');
revalidateTag('analytics', 'days');

// Egyedi lejárati idővel
revalidateTag('products', { expire: 3600 });

// ⚠️ Elavult — egyargumentumos forma
revalidateTag('blog-posts'); // Ne használd!

Szóval mikor melyiket válaszd?

  • updateTag — Ha a felhasználónak azonnal látnia kell a változást. Tipikus példák: kosár frissítése, profil módosítása, űrlap beküldése.
  • revalidateTag — Ha az esetleges konzisztencia (eventual consistency) elfogadható. Tipikus példák: CMS tartalom frissítése, globális beállítások változása.

Mikor használj "use cache"-t és mikor Suspense-t?

Ez az egyik leggyakrabban felmerülő kérdés a Cache Components kapcsán — és jogosan. Nézzük a döntési szempontokat:

Döntési útmutató

  • "use cache" → Az adat nem függ a kérés kontextusától (cookie-k, fejlécek), és teljesen rendben van, ha több kérés ugyanazt a cache-elt értéket kapja. Tipikus példák: blogbejegyzések, terméklista, statisztikák, navigáció.
  • <Suspense> → Az adat kérésenként változik, mert felhasználó-specifikus vagy valós idejű. Tipikus példák: személyre szabott tartalom, kosár, értesítések, valós idejű feed.
  • Mindkettő együtt → Az oldalon vannak cache-elhető és dinamikus részek is. Ez a PPR igazi ereje — és őszintén szólva a leggyakoribb minta bármilyen valódi alkalmazásban.

Fontos szabály: futásidejű adatok és "use cache" nem keverhető

A cookies(), headers() és searchParams nem használhatók "use cache" hatókörön belül, mert ezek kérés-specifikus adatok. Viszont van egy elegáns megoldás: kiolvasod az értékeket egy nem cache-elt komponensben, és argumentumként átadod a cache-elt függvénynek.

import { cookies } from 'next/headers';
import { Suspense } from 'react';

export default function ProfilePage() {
  return (
    <Suspense fallback={<div>Betöltés...</div>}>
      <ProfileContent />
    </Suspense>
  );
}

// Ez a komponens NEM cache-elt — futásidejű adatot olvas
async function ProfileContent() {
  const session = (await cookies()).get('session')?.value;
  // A session értékét átadjuk a cache-elt komponensnek
  return <CachedUserData sessionId={session} />;
}

// Ez a komponens CACHE-ELT — a sessionId a cache kulcs része
async function CachedUserData({ sessionId }: { sessionId: string }) {
  'use cache';
  const data = await fetchUserData(sessionId);
  return <div>{data.name} profilja</div>;
}

Migráció korábbi verziókról: Amit tudnod kell

Eltávolított route segment konfigurációk

A Cache Components engedélyezésével több korábbi route segment konfiguráció feleslegessé válik. Íme a legfontosabb megfeleltetések:

  • dynamic = "force-dynamic" → Nincs rá szükség. Minden oldal alapból dinamikus.
  • dynamic = "force-static" → Használj "use cache"-t cacheLife('max')-szal helyette.
  • revalidate = 3600 → Cseréld le cacheLife('hours')-ra.
  • fetchCache = "force-cache" → Felesleges. A "use cache" hatókörön belül minden fetch automatikusan cache-elődik.

A middleware.ts → proxy.ts átnevezés

Bár ez nem közvetlenül a Cache Components része, érdemes tudni róla: a Next.js 16-ban a middleware.ts fájlt proxy.ts-re nevezték át. A logika változatlan marad, de a middleware.ts már elavultnak számít. Ha olvastad a middleware biztonságáról szóló korábbi cikkünket, a benne leírt elvek továbbra is érvényesek — csak a fájlnevet és az exportált függvény nevét kell frissítened.

Navigáció és az Activity komponens

A cacheComponents flag engedélyezésével a Next.js a React <Activity> komponensét használja kliens oldali navigáció során. Ez azt jelenti, hogy az útvonalak közötti navigáláskor a komponens állapot megőrződik — ha visszanavigálsz egy korábban meglátogatott oldalra, az pontosan úgy jelenik meg, ahogy elhagytad. Űrlap kitöltések, kihajtott szekciók, scroll pozíció — minden megmarad. Nagyon kellemes fejlesztés.

Teljesítmény és jó gyakorlatok

A cache-elés arany szabályai

  1. Cache-elj a lehető legközelebb az adatforráshoz — Ne az egész oldalt cache-eld, ha csak egy adatlekérdezést kellene. A komponens vagy függvény szintű cache-elés sokkal finomabb kontrollt ad.
  2. Használj címkéket a CMS tartalmakhoz — Ha tartalomkezelő rendszered van, használj hosszú élettartamú cache-t cacheTag-ekkel kombinálva, és revalidateTag-gel frissíts, amikor a tartalom ténylegesen változik.
  3. A Suspense boundary-ket helyezd minél közelebb a dinamikus részhez — Minél kisebb a Suspense boundary hatóköre, annál több tartalom kerülhet a statikus héjba. Ez közvetlen hatással van a betöltési sebességre.
  4. Ne cache-elj futásidejű adatot — A cookie-k, fejlécek és keresési paraméterek mindig <Suspense>-be tartoznak, nem "use cache"-be.
  5. Egy cacheLife hívás futásonként — Ha feltételes logikád van (if/else), minden ágban meghívhatod, de futásonként mindig csak egy hajtódik végre.

Fontos korlátozás: Edge Runtime nem támogatott

A Cache Components a Node.js futtatókörnyezetet igényli. Ha Edge Runtime-ot próbálsz használni Cache Components-szel, hibát fogsz kapni. Ezt érdemes fejben tartani, különösen ha korábban Edge middleware-t használtál — a proxy.ts már Node.js runtime-on fut.

Gyakran Ismételt Kérdések (GYIK)

Mi történt az experimental.ppr flaggel a Next.js 16-ban?

A Next.js 16 eltávolította az experimental.ppr flaget és a route szintű experimental_ppr exportot. Helyettük a cacheComponents: true konfiguráció engedélyezi mind a Cache Components-et, mind a Partial Prerendering-et. Egyszerűbb, egységesebb — én is ezt hiányoltam a korábbi verziókból.

Mi a különbség az updateTag és a revalidateTag között?

Az updateTag kizárólag Server Actions-ben használható, és azonnal érvényteleníti a cache-t — a felhasználó rögtön látja a friss adatot (read-your-writes szemantika). A revalidateTag bárhol használható, és stale-while-revalidate viselkedést biztosít: a régi tartalom kiszolgálódik, miközben a háttérben megtörténik a frissítés. Interaktív műveleteknél (kosár, profil) az updateTag-et, háttérben frissülő tartalmaknál a revalidateTag-et válaszd.

Használhatok "use cache"-t cookie-kal vagy fejlécekkel együtt?

Közvetlenül nem — a cookies(), headers() és searchParams futásidejű adatok, amelyek nem cache-elhetők. De van megoldás: kiolvashatod az értékeket egy nem cache-elt komponensben, és argumentumként átadhatod egy "use cache" direktívával ellátott függvénynek. Az argumentum automatikusan a cache kulcs részévé válik, szóval a végeredmény pontosan az, amire szükséged van.

Hogyan működik a Cache Components a React Compiler-rel?

A React Compiler és a Cache Components egymástól függetlenül, de kiegészítő módon működnek. A React Compiler automatikusan memorizálja a komponenseket a felesleges újrarenderelés elkerülése érdekében (ez kliens oldalon történik), míg a Cache Components a szerver oldali adatlekérdezés és renderelés eredményét cache-eli. Mindkettő engedélyezhető a next.config.ts-ben, és az együttes használatuk komolyan megdobja a teljesítményt.

Muszáj átállnom Next.js 16-ra a Cache Components használatához?

Igen. A Cache Components és a "use cache" direktíva kizárólag a Next.js 16-ban érhető el. A korábbi verziók experimental.ppr és dynamicIO flagjei nem egyenértékűek — azok előzetes, kísérleti implementációk voltak. A stabil, végleges API a Next.js 16-ban debütált.

A Szerzőről Editorial Team

Our team of expert writers and editors.