キャッシュと再検証
ルートセグメント設定、unstable_cache、revalidateTag / revalidatePath、stale-while-revalidate。
ルートハンドラーのレスポンスとデータをキャッシュし、タイマーまたはオンデマンドで再検証します。古いエントリーは即座に返され、その間に1つのバックグラウンド更新が実行されます。
ルートハンドラー
GET ルートハンドラーはデフォルトで動的です。キャッシュを有効にするには、ルートセグメント設定を使います。
export const revalidate = 60;export async function GET() { const posts = await db.posts.latest(); return Response.json(posts);}| エクスポート | 動作 |
|---|---|
| (なし) | リクエストごとに実行します |
dynamic = "force-static" | 再検証されるまでキャッシュします |
revalidate = N | キャッシュし、N 秒後に古い状態になります |
revalidate = 0 または dynamic = "force-dynamic" | リクエストごとに実行します |
これ以外の dynamic の値は TX031 で失敗します。Text.jsはハンドラーがリクエストを読み取ったことを検出できないため、Next.jsの "error" も含まれます。
キャッシュされるハンドラーにはパス名ごとに1つのエントリーが割り当てられ、request はビルド時と同じ状態になります。クエリー文字列、ヘッダー、Cookie、本文はありません。これらは動的なハンドラーで読み取ってください。キャッシュされるのは GET と HEAD のみです。ステータスが 5xx のレスポンス、Set-Cookie ヘッダーを持つレスポンス、Cache-Control: private / no-store を持つレスポンスは返されますが、保存されません。
ヘッダー
キャッシュされたレスポンスには次のヘッダーが含まれます。
| ヘッダー | 値 |
|---|---|
x-textjs-cache | MISS、HIT、または STALE |
Cache-Control | s-maxage=N, stale-while-revalidate=<1 year − N>、または force-static の場合は s-maxage=31536000 |
ミドルウェアがリクエストに対してCookieの設定、リライト、リクエストヘッダーの上書きをすると、生成されるヘッダーは private, no-cache, no-store, max-age=0, must-revalidate になります。そのため、共有キャッシュが訪問者ごとの判断を再利用することはありません。ハンドラーが設定した Cache-Control ヘッダーは保持されます。
text dev ではセグメント設定は適用されますが、何も保存されないため、リクエストごとにハンドラーが実行されます。
データキャッシュ
任意の非同期関数をキャッシュするには、@textjs/app/cache の unstable_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 },);キーは、キーの構成要素と引数を値に基づいてエンコードしたものです(引数の undefined と null、Map、Set、Date、URL はそれぞれ区別されます)。キーの構成要素を指定しない場合は、関数のソースと作成された場所で識別されます。リクエストごとやループ内で作成する場合など、関数がクロージャーで値を参照するときは必ずキーの構成要素を渡してください。結果はJSONにシリアライズされるため、呼び出しごとに新しいコピーが返されます(Date オブジェクトは文字列として返されます)。シリアライズできない結果は例外をスローします。cache(fn, { key, tags, revalidate }) は同じ設定をオプションオブジェクトとして受け取ります。
キャッシュされたルートハンドラー内では、データエントリーのタグと最短の revalidate がルートに引き継がれます。"posts" を再検証すると、/api/posts も更新されます。revalidate: 0 を指定したデータ呼び出しがあると、そのルートは動的になります。
Stale-while-revalidate
エントリーが revalidate の期間を過ぎると、次のように動作します。
- 次のリクエストには古い値が即座に返されます(
STALE)。 - バックグラウンドで1つの更新が実行され、同時に届いたリクエストはその更新を共有します。
- 更新が例外をスローすると、古い値が引き続き返され、エラーがログに記録されます。
VercelとCloudflareでは、バックグラウンド更新は waitUntil() に登録されます。1年を超えたエントリーは常にフォアグラウンドで再計算されます。
オンデマンド再検証
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 });}| 呼び出し | 無効化の対象と動作 |
|---|---|
revalidateTag("posts") | posts タグが付いたすべてのエントリーを無効化し、次のリクエストで再計算します |
revalidateTag("posts", "max") | 古い状態としてマークし、次のリクエストには更新中の古い値を返します |
revalidatePath("/api/feed") | 指定したパスを無効化します |
revalidatePath("/api/posts/[id]") | 動的ルートのすべてのパスを無効化します |
revalidatePath("/api", "layout") | /api 配下のすべてを無効化します |
どちらもPromiseを返します。インメモリーストアでは即座に無効化されます。カスタムストアが非同期の場合は await してください。
カスタムキャッシュストア
デフォルトのストアは、サーバーインスタンスごとのインメモリーLRU(1,000エントリー、約50 MB)です。インスタンス間でキャッシュを共有するには、CacheStore をデフォルトエクスポートするモジュールを cacheHandler に指定します。
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;エントリーは通常のJSONです。代わりに setCacheStore(store) を使うと、実行時にストアを設定できます。
プラットフォームに関する注意事項
- 各サーバーレスインスタンス(Vercel Functions、Cloudflare Isolates)は、それぞれ独自のインメモリーキャッシュを持ちます。
Cache-Control: s-maxageを使うとプラットフォームのCDNでもレスポンスをキャッシュできますが、オンデマンド再検証ではCDNのキャッシュは削除されません。revalidateTagをすべてのインスタンスに反映する必要がある場合は、共有のcacheHandlerを使ってください。 - Cloudflareでは
nodejs_compatフラグが必要です(キャッシュがAsyncLocalStorageを使うためです)。デプロイを参照してください。