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:

app/api/posts/route.tsts
export const revalidate = 60;export async function GET() {  const posts = await db.posts.latest();  return Response.json(posts);}
EksportZachowanie
(brak)Uruchamianie przy każdym żądaniu
dynamic = "force-static"Buforowanie do rewalidacji
revalidate = NBuforowanie; 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łówekWartość
x-textjs-cacheMISS, HIT lub STALE
Cache-Controls-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:

lib/posts.tsts
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:

  1. Następne żądanie natychmiast otrzymuje nieaktualną wartość (STALE).
  2. W tle trwa jedno odświeżanie; współbieżne żądania korzystają z tego samego odświeżania.
  3. 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

app/api/revalidate/route.tsts
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łanieUnieważ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:

text.config.tsts
export default defineConfig({ cacheHandler: "./cache-handler.ts" });
cache-handler.tsts
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-maxage pozwala także sieci CDN platformy buforować odpowiedzi; rewalidacja na żądanie nie czyści sieci CDN. Użyj współdzielonego cacheHandler, gdy revalidateTag musi dotrzeć do każdej instancji.
  • Cloudflare wymaga flagi nodejs_compat (pamięć podręczna korzysta z AsyncLocalStorage). Zobacz Wdrażanie.
Edytuj tę stronę w serwisie GitHub