use cache w Next.js: klucz, czas życia i updateTag
Jak działa use cache w Next.js, na żywym demo: klucz cache tworzą argumenty, cacheLife ustala czas życia wpisu, a updateTag czyści wpisy jednego tagu.

Wpisz "use cache" na początku funkcji asynchronicznej, a Next.js zapisze jej wynik pod kluczem zbudowanym z builda, funkcji i jej argumentów: wywołania z tymi samymi argumentami dzielą jeden wpis, inne argumenty dostają własny. Wpis żyje tak długo, jak pozwala profil cacheLife (otwiera się w nowej karcie) (bez wywołania profil default odświeża go po 15 minutach i nigdy nie wygasza po czasie), a Server Action, która wywoła updateTag z tagiem wpisu z cacheTag, czyści tylko wpisy z tym tagiem. Wszystkie trzy rzeczy pokazujemy na jednym demo na Next.js 16.4.0, a jego test w Playwright sprawdza każde twierdzenie o demo z tego tekstu. We własnej aplikacji używaj 16.4.1 lub nowszej wersji: naprawia krytyczne RCE w funkcjach use cache, jedną z poprawek RCE w React i Next.js z października 2026.
Jak działa use cache: kiedy dwa wywołania dzielą wpis
Dokumentacja use cache (otwiera się w nowej karcie) wymienia, co po serializacji trafia do klucza wpisu:
| Składnik klucza | Co to znaczy w praktyce |
|---|---|
Build ID (albo deploymentId) | Nowy deploy zaczyna każdy wpis od nowa. |
| ID funkcji | Skrót położenia i sygnatury funkcji: te same argumenty w innej funkcji z cache to inny wpis. |
| Serializowalne argumenty | Każdy inny argument (albo zestaw propsów) to osobny wpis. |
| Zmienne z domknięcia | Wartości czytane z zewnętrznego zakresu trafiają do klucza jak argumenty. |
| Skrót odświeżenia HMR | Tylko w trybie deweloperskim: edycja kodu unieważnia cache. |
Demo ma jedną funkcję z cache. Tworzy id i znacznik czasu w swoim ciele, więc nowe id oznacza, że ciało wykonało się ponownie:
import { cacheLife, cacheTag } from "next/cache"; export type City = "gdansk" | "krakow"; // One cached function. The entry's key is built from its argument, so getStamp("gdansk") and// getStamp("krakow") are two entries, and every call with "gdansk" shares one.export async function getStamp(city: City) { "use cache"; cacheLife("hours"); // revalidate after 1 hour, expire after 1 day cacheTag(`city-${city}`); // updateTag("city-gdansk") clears this city's entry only // Produced inside the cached body: a new id means the body ran again. return { id: crypto.randomUUID().slice(0, 8), at: new Date().toISOString() };}Strona wywołuje ją trzy razy: dwa razy z "gdansk" i raz z "krakow". Oba wiersze Gdańska pokazują to samo id i czas, bo ten sam argument zwraca ten sam wpis. Kraków ma inne id: miasta mają osobne wpisy. Wywołania nie różnią się niczym innym, więc o wpisie decyduje sam argument.
Argumenty muszą być serializowalne: typy proste, zwykłe obiekty, tablice, Date, Map i Set przejdą, instancje klas i obiekty URL nie, a funkcje i JSX można tylko przekazać dalej (otwiera się w nowej karcie) bez zaglądania do nich. Przekazuj id rekordu, a nie obiekt klienta, a klucz zostanie mały i stabilny.
Jak długo żyje wpis use cache
Przeładuj demo: id i czasy z cache zostają te same, a wiersz „Not cached”, który czeka na żądanie przez connection(), zmienia się przy każdym wczytaniu. Z wyłączonym JavaScriptem przeglądarka pokazuje HTML, który serwer wysłał najpierw: id z cache już w nim są, a wiersz bez cache pokazuje tylko placeholder. Zgadza się to z dokumentacją: przy Cache Components prerender wypełnia wpis (otwiera się w nowej karcie), a jego wynik staje się częścią statycznej powłoki strony.
Jak długo tam zostaje, ustala profil cacheLife. Wbudowane profile (otwiera się w nowej karcie) według dokumentacji z 19 października 2026:
| Profil | stale (przeglądarka) | revalidate (serwer) | expire |
|---|---|---|---|
default (bez cacheLife) | 5 minut | 15 minut | nigdy |
seconds | 30 sekund | 1 sekunda | 1 minuta |
minutes | 5 minut | 1 minuta | 1 godzina |
hours (w demo) | 5 minut | 1 godzina | 1 dzień |
days | 5 minut | 1 dzień | 1 tydzień |
weeks | 5 minut | 1 tydzień | 30 dni |
max | 5 minut | 30 dni | 1 rok |
revalidate to moment, po którym kolejne żądanie dostaje kopię z cache, a świeża powstaje w tle; expire to czas, przez jaki wpis może nie być pobierany, zanim następne żądanie poczeka na świeży render. stale dotyczy przeglądarki: router po stronie klienta trzyma wynik tak długo, ale co najmniej 30 sekund. To ten sam cache, do którego trafiają prefetchowane strony; tę część zmierzył nasz benchmark prefetchu w Next.js.
Krótki czas życia zmienia, gdzie wpis może trafić. Według progów prerenderu w cacheLife (otwiera się w nowej karcie) expire poniżej 5 minut albo revalidate równe 0 robi z niego dynamiczną dziurę renderowaną w czasie żądania, więc z gotowych profili tylko seconds nigdy nie trafia do statycznej powłoki. Taki komponent owiń w <Suspense>.
Jak wyczyścić wpis use cache przez updateTag
updateTag (otwiera się w nowej karcie) można wywołać tylko w Server Action. Od razu wygasza każdy wpis z danym tagiem, a następne żądanie czeka na świeże dane, zamiast dostać starą kopię. Akcja w demo bierze miasto z przycisku i czyści tag tego miasta:
"use server"; import { updateTag } from "next/cache"; import type { City } from "./get-stamp"; const cities: City[] = ["gdansk", "krakow"]; // A Server Action: updateTag works only here. It expires every entry tagged with this city, so// the next render runs getStamp for that city again and reuses the other city's entry.export async function clearCity(formData: FormData) { const city = formData.get("city"); if (!cities.includes(city as City)) return; updateTag(`city-${city}`);}Kliknij Clear przy Gdańsku: bez przeładowania Gdańsk dostaje nowe id (w obu wierszach, bo dzielą wpis), a Kraków zachowuje swoje. Zasięg czyszczenia wyznacza jeden tag na miasto: updateTag wygasza każdy wpis z tym tagiem, więc wspólny tag obu miast wyczyściłby też Kraków.
Sama strona to zwykłe Server Components: wiersze z cache, wiersz bez cache w <Suspense> i formularz dla każdego miasta, który wysyła akcję:
import { connection } from "next/server";import { Suspense } from "react"; import { clearCity } from "./actions";import { getStamp, type City } from "./get-stamp";import styles from "./stamps.module.css"; export default function Page() { return ( <> <div className={styles.rows}> <CachedRow city="gdansk" name="Gdansk" clearable /> <CachedRow city="gdansk" name="Gdansk again" /> <CachedRow city="krakow" name="Krakow" clearable /> <Suspense fallback={<Row name="Not cached" call="connection()" pending />}> <UncachedRow /> </Suspense> </div> <p className={styles.reload}> <a href="/demos/nextjs-use-cache">Reload</a>, then clear one city. </p> </> );} // Same function, same argument, same entry: both Gdansk rows show one id.async function CachedRow({ city, name, clearable }: { city: City; name: string; clearable?: boolean }) { const stamp = await getStamp(city); return <Row name={name} call={`getStamp("${city}")`} stamp={stamp} clear={clearable ? city : undefined} />;} // No cache: connection() waits for a request, so this runs on every load.async function UncachedRow() { await connection(); const stamp = { id: crypto.randomUUID().slice(0, 8), at: new Date().toISOString() }; return <Row name="Not cached" call="connection()" stamp={stamp} />;} type RowProps = { name: string; call: string; stamp?: { id: string; at: string }; clear?: City; pending?: boolean }; function Row({ name, call, stamp, clear, pending }: RowProps) { return ( <section className={styles.row} aria-label={name}> <div> <h2>{name}</h2> <code>{call}</code> </div> <p className={styles.stamp}> {stamp ? ( <> <output>{stamp.id}</output> <time dateTime={stamp.at}>{stamp.at.slice(11, 19)} UTC</time> </> ) : ( pending && <span>rendering…</span> )} </p> {clear && ( <form action={clearCity}> <button name="city" value={clear}> Clear </button> </form> )} </section> );}W webhooku albo Route Handlerze, gdzie updateTag rzuca błąd, użyj revalidateTag(tag, "max") (otwiera się w nowej karcie): serwuje kopię z cache, a świeża powstaje w tle.
Jak dodać use cache do własnej aplikacji Next.js
Włącz Cache Components
Ustaw
cacheComponents: truewnext.config.ts.use cachenależy do Cache Components i działa tylko z tą opcją.Cache'uj funkcję, nie żądanie
Wpisz
"use cache"w pierwszej linii funkcji asynchronicznej, która ładuje wspólne dane. Cookies, nagłówki i parametry wyszukiwania czytaj poza nią i przekazuj potrzebne wartości jako argumenty.Ustaw cacheLife wprost
Wywołaj
cacheLife("hours")(albo profil pasujący do tego, jak często zmieniają się dane) w każdej funkcji z cache, żeby czas życia był widoczny tam, gdzie ją zdefiniowano, zamiast zależeć oddefault.Otaguj wpis tym, co go zmienia
Wywołaj
cacheTag(otwiera się w nowej karcie) z jednym tagiem na rekord, a gdy rekord pokazuje też lista, dodaj drugi tag dla listy: jeden wpis może mieć kilka tagów.Czyść tag tam, gdzie zmieniają się dane
Po zapisie w Server Action wywołaj
updateTagz tagiem rekordu. Z webhooka albo Route Handlera wywołajrevalidateTag(tag, "max").Sprawdź to na buildzie produkcyjnym
Uruchom
next buildinext start, a potem przeładuj stronę i wyczyść tag jak w demo. W trybie deweloperskim skrót HMR jest częścią klucza, więc każda edycja czyści cache.
Konfiguracja to jedna linia:
const nextConfig = { cacheComponents: true };Co psuje use cache
cookies() i headers() w funkcji z cache
Funkcja z cache nie może czytać cookies(), headers() ani searchParams, a zakaz obejmuje cały stos wywołań: helper, który czyta jedną z tych wartości, kończy się tym samym błędem next-request-in-use-cache (otwiera się w nowej karcie). Na trasie renderowanej dynamicznie błąd wychodzi dopiero przy jej uruchomieniu, więc build może przejść, a next start się wyłożyć. Odczytaj wartość w komponencie i przekaż ją jako argument: wtedy trafi też do klucza.
Gdzie pasuje use cache: private
Gdy wyniesienie odczytu żądania na zewnątrz jest niepraktyczne, 'use cache: private' (otwiera się w nowej karcie) pozwala na cookies(), headers() i searchParams w zakresie z cache. To inny cache: na produkcji pasujące wywołania w obrębie jednego żądania dzielą wynik, ale nic nie zostaje zapisane na serwerze między żądaniami, a funkcja działa w czasie żądania, poza statyczną powłoką. Przeglądarka może trzymać wynik przez czas stale. connection() jest zakazane w obu dyrektywach.
Hosting serverless i nowe deploye
Domyślny handler trzyma wpisy w pamięci. Na serverless dokumentacja mówi, że wpisy zwykle nie przetrwają między żądaniami (otwiera się w nowej karcie), bo każde może trafić na inną instancję, a cache z czasu builda działa normalnie. Na własnym serwerze jeden proces je trzyma, a limit ustawia cacheMaxMemorySize. Jeśli na serverless liczą się wpisy tworzone w czasie żądania, dokumentacja wskazuje 'use cache: remote' i handler wspólny dla instancji. A że build ID jest w kluczu, każdy deploy zaczyna z pustym cache.
Brak cacheLife w zagnieżdżonym cache
Funkcja z use cache bez własnego cacheLife dostaje profil default, a wewnętrzny cache z krótszym czasem życia może go skrócić, jak opisują zasady zagnieżdżania w cacheLife (otwiera się w nowej karcie). Zagnieżdżenie cache seconds w zewnętrznym bez cacheLife kończy build błędem podczas prerenderu. Jawne cacheLife w każdej funkcji z cache zapobiega obu sytuacjom.
Z czego składa się klucz cache w use cache?
Z zserializowanych danych wejściowych wpisu: build ID (albo deploymentId, jeśli go ustawisz), skrótu położenia i sygnatury funkcji oraz jej serializowalnych argumentów lub propsów. Zmienne, które funkcja czyta z zewnętrznego zakresu, też trafiają do klucza. W trybie deweloperskim dochodzi skrót odświeżenia HMR.
Czy use cache działa na serverless i na Vercel?
Cache z czasu builda działa tam normalnie, więc wynik prerenderowany do statycznej powłoki jest serwowany tak, jak go zbudowano. Wpisy tworzone w czasie żądania trafiają do domyślnego cache w pamięci, który na serverless zwykle nie przetrwa między żądaniami, bo każde może trafić na inną instancję. Dla cache wspólnego dla instancji dokumentacja Next.js wskazuje 'use cache: remote'.
Czym różni się use cache od unstable_cache?
Wpisy use cache mają w kluczu build ID, więc żaden nie przechodzi na nowy deploy. Dla danych, które muszą przetrwać deploy, dokumentacja Next.js poleca unstable_cache (dla funkcji innych niż fetch) albo cache fetch.
Czym różni się cache z Reacta od use cache?
cache z Reacta zapamiętuje wynik funkcji na czas jednego żądania na serwerze i jest unieważniany przy każdym nowym. use cache trzyma wynik pod swoim kluczem między żądaniami, aż cacheLife albo tag zakończy wpis. Wartości zapisane przez cache z Reacta poza funkcją z use cache nie są w niej widoczne, więc dane przekazuj jako argumenty.
Jak włączyć use cache w Next.js 16?
Ustaw cacheComponents: true w next.config.ts. use cache należy do Cache Components i działa tylko z tą opcją.
