Pamięć podręczna i rewalidacja
Konfiguracja segmentów tras, unstable_cache, revalidateTag / revalidatePath i stale-while-revalidate.
Zapisuj odpowiedzi handlerów tras i dane w pamięci podręcznej, a następnie rewaliduj je okresowo lub na żądanie. Nieaktualne wpisy są zwracane natychmiast, podczas gdy w tle trwa jedno odświeżanie.
Handlery tras
Handlery tras GET są domyślnie dynamiczne. Włącz buforowanie w konfiguracji segmentu trasy:
export const revalidate = 60;export async function GET() { const posts = await db.posts.latest(); return Response.json(posts);}| Eksport | Zachowanie |
|---|---|
| (brak) | Uruchamianie przy każdym żądaniu |
dynamic = "force-static" | Buforowanie do rewalidacji |
revalidate = N | Buforowanie; utrata aktualności po N sekundach |
revalidate = 0 lub dynamic = "force-dynamic" | Uruchamianie przy każdym żądaniu |
Inne wartości dynamic powodują błąd TX031. Dotyczy to także wartości "error" z Next.js, ponieważ Text.js nie potrafi wykryć, kiedy handler odczytuje żądanie.
Buforowane handlery mają jeden wpis na ścieżkę, a ich request wygląda tak jak podczas budowania: bez ciągu zapytania, nagłówków, plików cookie ani treści. Odczytuj je w handlerze dynamicznym. Buforowane są tylko GET i HEAD. Odpowiedzi ze statusem 5xx, nagłówkiem Set-Cookie lub Cache-Control: private / no-store są zwracane, ale nigdy nie są zapisywane.
Nagłówki
Buforowane odpowiedzi zawierają:
| Nagłówek | Wartość |
|---|---|
x-textjs-cache | MISS, HIT lub STALE |
Cache-Control | s-maxage=N, stale-while-revalidate=<1 year − N> lub s-maxage=31536000 dla force-static |
Gdy middleware ustawia pliki cookie, przepisuje żądanie lub nadpisuje jego nagłówki, wygenerowany nagłówek przyjmuje wartość private, no-cache, no-store, max-age=0, must-revalidate, więc współdzielone pamięci podręczne nigdy nie odtwarzają decyzji podjętej dla konkretnego odwiedzającego. Nagłówek Cache-Control ustawiony przez handler zostaje zachowany.
W text dev konfiguracja segmentu obowiązuje, ale nic nie jest zapisywane, więc każde żądanie uruchamia handler.
Pamięć podręczna danych
Buforuj dowolną funkcję asynchroniczną za pomocą unstable_cache z @textjs/app/cache:
import { unstable_cache } from "@textjs/app/cache";export const getPosts = unstable_cache( async (author: string) => db.posts.byAuthor(author), ["posts"], { tags: ["posts"], revalidate: 300 },);Klucz składa się z części klucza i argumentów zakodowanych według wartości (argumenty undefined i null, Map, Set, Date i URL pozostają rozróżnialne). Bez części klucza funkcję identyfikują jej kod źródłowy i miejsce utworzenia; przekazuj części klucza zawsze, gdy funkcja przechwytuje wartości w domknięciu, na przykład gdy jest tworzona dla każdego żądania lub w pętli. Wyniki są serializowane do JSON, więc każde wywołanie zwraca nową kopię (a obiekty Date wracają jako ciągi znaków); wynik, którego nie można serializować, powoduje zgłoszenie wyjątku. cache(fn, { key, tags, revalidate }) przyjmuje te same ustawienia w postaci obiektu opcji.
W buforowanym handlerze trasy wpisy danych przekazują swoje tagi i najkrótszy czas revalidate do trasy: rewalidacja "posts" odświeża także /api/posts. Wywołanie danych z revalidate: 0 sprawia, że trasa staje się dynamiczna.
Stale-while-revalidate
Gdy wiek wpisu przekroczy revalidate:
- Następne żądanie natychmiast otrzymuje nieaktualną wartość (
STALE). - W tle trwa jedno odświeżanie; współbieżne żądania korzystają z tego samego odświeżania.
- Jeśli odświeżanie zgłosi wyjątek, nieaktualna wartość jest nadal zwracana, a błąd jest zapisywany w dzienniku.
Na platformach Vercel i Cloudflare odświeżanie w tle jest rejestrowane za pomocą waitUntil(). Wpisy starsze niż rok są zawsze przeliczane na pierwszym planie.
Rewalidacja na żądanie
import { revalidatePath, revalidateTag } from "@textjs/app/cache";export async function POST(request: Request) { // Anyone can POST here: require a secret shared with your CMS or webhook. if (request.headers.get("authorization") !== `Bearer ${process.env.REVALIDATE_SECRET}`) { return new Response("Unauthorized", { status: 401 }); } await revalidateTag("posts"); await revalidatePath("/api/feed"); return Response.json({ revalidated: true });}| Wywołanie | Unieważnia |
|---|---|
revalidateTag("posts") | Wszystko z tagiem posts; następne żądanie przelicza wynik |
revalidateTag("posts", "max") | Oznacza jako nieaktualne; następne żądanie otrzymuje nieaktualną wartość podczas odświeżania |
revalidatePath("/api/feed") | Tę ścieżkę |
revalidatePath("/api/posts/[id]") | Każdą ścieżkę trasy dynamicznej |
revalidatePath("/api", "layout") | Wszystko pod /api |
Obie funkcje zwracają obietnicę. W przypadku magazynu w pamięci unieważnienie jest natychmiastowe; użyj await, gdy własny magazyn jest asynchroniczny.
Własne magazyny pamięci podręcznej
Domyślny magazyn to LRU w pamięci (1000 wpisów, około 50 MB) dla każdej instancji serwera. Aby współdzielić pamięć podręczną między instancjami, wskaż w cacheHandler moduł, którego domyślnym eksportem jest CacheStore:
export default defineConfig({ cacheHandler: "./cache-handler.ts" });import type { CacheStore } from "@textjs/app/cache";const store: CacheStore = { async get(key) { return redis.get(key).then((v) => (v ? JSON.parse(v) : undefined)); }, async set(key, entry) { await redis.set(key, JSON.stringify(entry)); }, async delete(key) { await redis.del(key); }, async markTags(tags, mode, at) { for (const t of tags) await redis.hset(`tag:${t}`, mode, at); }, async getTagState(tags) { /* max expiredAt / staleAt across tags */ return { expiredAt: 0, staleAt: 0 }; },};export default store;Wpisy mają postać zwykłego JSON. Alternatywnie setCacheStore(store) instaluje magazyn w trakcie działania.
Uwagi dotyczące platform
- Każda instancja serverless (funkcje Vercel, izolaty Cloudflare) ma własną pamięć podręczną w pamięci operacyjnej.
Cache-Control: s-maxagepozwala także sieci CDN platformy buforować odpowiedzi; rewalidacja na żądanie nie czyści sieci CDN. Użyj współdzielonegocacheHandler, gdyrevalidateTagmusi dotrzeć do każdej instancji. - Cloudflare wymaga flagi
nodejs_compat(pamięć podręczna korzysta zAsyncLocalStorage). Zobacz Wdrażanie.