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
fetchhí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:
- 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).
- 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.
- Becsomagolod
- 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:
- Az egész oldal statikus → gyors, de a dinamikus részek nem frissülnek.
- 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"-tcacheLife('max')-szal helyette.revalidate = 3600→ Cseréld lecacheLife('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
- 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.
- Használj címkéket a CMS tartalmakhoz — Ha tartalomkezelő rendszered van, használj hosszú élettartamú cache-t
cacheTag-ekkel kombinálva, ésrevalidateTag-gel frissíts, amikor a tartalom ténylegesen változik. - 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.
- Ne cache-elj futásidejű adatot — A cookie-k, fejlécek és keresési paraméterek mindig
<Suspense>-be tartoznak, nem"use cache"-be. - 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.