کش و اعتبارسنجی مجدد
پیکربندی بخش مسیر، unstable_cache، revalidateTag / revalidatePath و stale-while-revalidate.
پاسخهای کنترلگر مسیر و دادهها را در حافظهٔ نهان ذخیره کنید، سپس آنها را با زمانبندی یا بنا به درخواست دوباره اعتبارسنجی کنید. ورودیهای قدیمی بلافاصله ارائه میشوند و همزمان یک بهروزرسانی در پسزمینه اجرا میشود.
کنترلگرهای مسیر
کنترلگرهای مسیر 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 مواجه میشوند. مقدار "error" در Next.js نیز شامل این موارد است، زیرا Text.js نمیتواند تشخیص دهد که کنترلگر چه زمانی درخواست را میخواند.
کنترلگرهای ذخیرهشده در حافظهٔ نهان برای هر نام مسیر یک ورودی دارند و مقدار request آنها مانند زمان ساخت است: بدون رشتهٔ پرسوجو، سرآیند، کوکی یا بدنه. این موارد را در یک کنترلگر پویا بخوانید. فقط 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>، یا s-maxage=31536000 برای force-static |
وقتی middleware برای درخواست کوکی تنظیم میکند، بازنویسی انجام میدهد یا سرآیندهای درخواست را جایگزین میکند، سرآیند تولیدشده به private, no-cache, no-store, max-age=0, must-revalidate تبدیل میشود تا حافظههای نهان مشترک هرگز تصمیم مختص یک بازدیدکننده را بازپخش نکنند. سرآیند Cache-Control که کنترلگر تنظیم کرده باشد حفظ میشود.
در text dev، پیکربندی بخش اعمال میشود اما چیزی ذخیره نمیشود، بنابراین هر درخواست کنترلگر را اجرا میکند.
حافظهٔ نهان داده
هر تابع ناهمگام را با unstable_cache از @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 },);کلید از بخشهای کلید بهعلاوهٔ آرگومانها تشکیل میشود که بر اساس مقدار کدگذاری شدهاند (آرگومانهای undefined و null، Map، Set، Date و URL متمایز میمانند). بدون بخشهای کلید، کد منبع تابع و محل ایجاد آن هویتش را مشخص میکنند؛ هر زمان تابع مقادیری را در بستار خود نگه میدارد، بخشهای کلید را ارسال کنید، برای مثال وقتی برای هر درخواست یا در یک حلقه ایجاد میشود. نتایج به JSON سریالسازی میشوند، بنابراین هر فراخوانی یک نسخهٔ تازه برمیگرداند (و اشیای Date بهصورت رشته برمیگردند)؛ نتیجهای که قابل سریالسازی نباشد خطا ایجاد میکند. تابع cache(fn, { key, tags, revalidate }) همین تنظیمات را بهصورت یک شیء گزینهها میپذیرد.
درون یک کنترلگر مسیر ذخیرهشده در حافظهٔ نهان، ورودیهای داده برچسبها و کوتاهترین مقدار revalidate خود را به مسیر منتقل میکنند: اعتبارسنجی مجدد "posts" مسیر /api/posts را نیز بهروز میکند. فراخوانی داده با revalidate: 0 مسیر را پویا میکند.
ارائهٔ مقدار قدیمی هنگام اعتبارسنجی مجدد
وقتی عمر یک ورودی از revalidate بیشتر شود:
- درخواست بعدی بلافاصله مقدار قدیمی را دریافت میکند (
STALE). - یک بهروزرسانی در پسزمینه اجرا میشود؛ درخواستهای همزمان از همان بهروزرسانی استفاده میکنند.
- اگر بهروزرسانی خطا ایجاد کند، ارائهٔ مقدار قدیمی ادامه مییابد و خطا ثبت میشود.
در Vercel و Cloudflare، بهروزرسانیهای پسزمینه با waitUntil() ثبت میشوند. ورودیهای قدیمیتر از یک سال همیشه در پیشزمینه دوباره محاسبه میشوند.
اعتبارسنجی مجدد بنا به درخواست
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). برای اشتراک حافظهٔ نهان میان نمونهها، مقدار cacheHandler را روی ماژولی تنظیم کنید که یک 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;ورودیها JSON معمولی هستند. تابع setCacheStore(store) بهجای آن، یک ذخیرهساز را در زمان اجرا نصب میکند.
نکات پلتفرم
- هر نمونهٔ بدون سرور (توابع Vercel، محیطهای ایزولهٔ Cloudflare) حافظهٔ نهان درونحافظهای خودش را دارد. سرآیند
Cache-Control: s-maxageبه CDN پلتفرم نیز اجازه میدهد پاسخها را در حافظهٔ نهان ذخیره کند؛ اعتبارسنجی مجدد بنا به درخواست، حافظهٔ نهان CDN را پاک نمیکند. وقتیrevalidateTagباید به همهٔ نمونهها برسد، از یکcacheHandlerمشترک استفاده کنید. - Cloudflare به پرچم
nodejs_compatنیاز دارد (حافظهٔ نهان ازAsyncLocalStorageاستفاده میکند). به استقرار مراجعه کنید.