Cache dan revalidasi

Konfigurasi segmen route, unstable_cache, revalidateTag / revalidatePath, dan stale-while-revalidate.

Simpan respons route handler dan data dalam cache, lalu validasi ulang secara berkala atau sesuai permintaan. Entri kedaluwarsa langsung disajikan sementara satu pembaruan berjalan di latar belakang.

Route handler

Route handler GET bersifat dinamis secara default. Aktifkan cache melalui konfigurasi segmen route:

app/api/posts/route.tsts
export const revalidate = 60;export async function GET() {  const posts = await db.posts.latest();  return Response.json(posts);}
EksporPerilaku
(tidak ada)Berjalan pada setiap permintaan
dynamic = "force-static"Disimpan dalam cache hingga divalidasi ulang
revalidate = NDisimpan dalam cache; kedaluwarsa setelah N detik
revalidate = 0 atau dynamic = "force-dynamic"Berjalan pada setiap permintaan

Nilai dynamic lainnya gagal dengan TX031. Ini termasuk "error" milik Next.js, karena Text.js tidak bisa mendeteksi kapan handler membaca permintaan.

Handler yang menggunakan cache mendapat satu entri per pathname, dan request-nya menyerupai kondisi saat build: tanpa string kueri, header, cookie, atau body. Baca semua itu dalam handler dinamis. Hanya GET dan HEAD yang disimpan dalam cache. Respons dengan status 5xx, header Set-Cookie, atau Cache-Control: private / no-store dikembalikan tetapi tidak pernah disimpan.

Header

Respons dari cache menyertakan:

HeaderNilai
x-textjs-cacheMISS, HIT, atau STALE
Cache-Controls-maxage=N, stale-while-revalidate=<1 year − N>, atau s-maxage=31536000 untuk force-static

Ketika middleware menetapkan cookie, menulis ulang permintaan, atau menimpa header permintaan, header yang dihasilkan menjadi private, no-cache, no-store, max-age=0, must-revalidate, sehingga cache bersama tidak pernah menggunakan kembali keputusan khusus pengunjung. Header Cache-Control yang ditetapkan handler tetap dipertahankan.

Dalam text dev, konfigurasi segmen berlaku tetapi tidak ada yang disimpan, sehingga setiap permintaan menjalankan handler.

Cache data

Simpan fungsi asinkron apa pun dalam cache dengan unstable_cache dari @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 },);

Kunci terdiri dari bagian kunci ditambah argumen, yang dikodekan berdasarkan nilai (argumen undefined dan null, Map, Set, Date, dan URL tetap dibedakan). Tanpa bagian kunci, kode sumber fungsi dan tempat pembuatannya menjadi identitasnya; berikan bagian kunci setiap kali fungsi menangkap nilai dari lingkup luarnya, misalnya saat dibuat per permintaan atau dalam perulangan. Hasil diserialisasi ke JSON, sehingga setiap pemanggilan mengembalikan salinan baru (dan objek Date dikembalikan sebagai string); hasil yang tidak bisa diserialisasi memicu error. cache(fn, { key, tags, revalidate }) menerima pengaturan yang sama sebagai objek opsi.

Di dalam route handler yang menggunakan cache, entri data meneruskan tag dan revalidate terpendeknya ke route: memvalidasi ulang "posts" juga memperbarui /api/posts. Pemanggilan data dengan revalidate: 0 membuat route menjadi dinamis.

Stale-while-revalidate

Setelah usia entri melebihi revalidate:

  1. Permintaan berikutnya langsung menerima nilai kedaluwarsa (STALE).
  2. Satu pembaruan berjalan di latar belakang; permintaan bersamaan berbagi pembaruan tersebut.
  3. Jika pembaruan memicu error, nilai kedaluwarsa tetap disajikan dan error dicatat dalam log.

Di Vercel dan Cloudflare, pembaruan latar belakang didaftarkan dengan waitUntil(). Entri yang berusia lebih dari satu tahun selalu dihitung ulang di latar depan.

Validasi ulang sesuai permintaan

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 });}
PemanggilanYang dibatalkan validitasnya
revalidateTag("posts")Semua yang diberi tag posts; permintaan berikutnya menghitung ulang
revalidateTag("posts", "max")Menandainya kedaluwarsa; permintaan berikutnya menerima nilai kedaluwarsa selama pembaruan berjalan
revalidatePath("/api/feed")Path tersebut
revalidatePath("/api/posts/[id]")Setiap path dari route dinamis
revalidatePath("/api", "layout")Semua yang berada di bawah /api

Keduanya mengembalikan promise. Dengan penyimpanan dalam memori, pembatalan validitas berlangsung seketika; gunakan await jika penyimpanan kustom bersifat asinkron.

Penyimpanan cache kustom

Penyimpanan default adalah LRU dalam memori (1.000 entri, sekitar 50 MB) per instans server. Untuk berbagi cache antarinstans, arahkan cacheHandler ke modul yang mengekspor CacheStore sebagai ekspor default:

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;

Entri berupa JSON biasa. Sebagai alternatif, setCacheStore(store) memasang penyimpanan saat runtime.

Catatan platform

  • Setiap instans serverless (fungsi Vercel, isolate Cloudflare) memiliki cache dalam memorinya sendiri. Cache-Control: s-maxage memungkinkan CDN platform menyimpan respons dalam cache juga; validasi ulang sesuai permintaan tidak menghapus cache CDN. Gunakan cacheHandler bersama ketika revalidateTag harus menjangkau setiap instans.
  • Cloudflare memerlukan flag nodejs_compat (cache menggunakan AsyncLocalStorage). Lihat Deploy.
Edit halaman ini di GitHub