رفتن به محتوا

نکتهٔ حرفه‌ای Nuxt: تصمیم‌های کش و رندر را در routeRules بگذارید، نه در هندلرها

۶ دقیقه مطالعه
nuxt
pro-tip
nitro
caching

در ۷۲ ساعت گذشته چیزی آن‌قدر مهم منتشر نشد که پایهٔ یک پست شود. وصله‌های Vue 3.5.44 و Vite 8.3.4 آمدند، اما اولی یادداشت انتشار خواناییِ ندارد و دومی ادامهٔ جریانی است که قبلاً پوشش داده‌ایم. پس امروز یک نکتهٔ حرفه‌ای Nuxt داریم. نکته‌های قبلی همه دربارهٔ واکشی داده در سمت کلاینت بودند؛ این یکی دربارهٔ سمت سرور است: رندر و کش به‌ازای هر مسیر.

همهٔ مطالب زیر با مستندات رندر Nuxt و مستندات مسیریابی Nitro تطبیق داده شده است.

اشتباه رایج

تصمیم‌های کش پراکنده می‌شوند. یک setHeader('cache-control', ...) در یک مسیر سرور، یک defineCachedEventHandler در دیگری، یک prerender: true گم‌شده در nitro.prerender.routes، و صفحه‌ای کلاینت‌محور که هنوز سمت سرور رندر می‌شود چون کسی آن را جایی ننوشته است.

server/api/products.get.ts
export default defineEventHandler(async (event) => {
    // Cache policy lives inside the handler, invisible from the config
    setHeader(event, "cache-control", "s-maxage=60, stale-while-revalidate");
    return await $fetch("https://api.example.com/products");
});

مشکل این نیست که کد غلط است. مشکل این است که بدون خواندن تک‌تک هندلرها نمی‌توان گفت کدام مسیرها کش می‌شوند، چقدر و کجا. سیاستی که هفتگی عوض می‌شود (TTLها، بخش‌های ایستا) با منطقی که ماهانه عوض می‌شود قاطی شده است.

الگوی بهتر: یک جدول

routeRules در nuxt.config.ts رفتار را به الگوی مسیر وصل می‌کند. مستندات رندر ترکیبی را تنظیم قاعدهٔ کش و رندر برای هر مسیر توصیف می‌کنند؛ Nuxt میدلویر مناسب را ثبت می‌کند و مسیرها را با هندلرهای کش Nitro می‌پوشاند.

nuxt.config.ts
export default defineNuxtConfig({
    routeRules: {
        "/": { prerender: true },
        "/products": { swr: true },
        "/products/**": { swr: 3600 },
        "/blog": { isr: 3600 },
        "/blog/**": { isr: true },
        "/admin/**": { ssr: false },
        "/api/**": { cors: true },
        "/old-page": { redirect: "/new-page" },
    },
});

این مثال از مستندات Nuxt است و نکته همین است: کل راهبرد رندر و کش سایت از بالا به پایین در یک‌جا خوانده می‌شود. هندلر قبلی به همان واکشی کوچک می‌شود.

هر گزینه چه می‌کند

طبق مستندات Nuxt:

  • prerender: true مسیر را در زمان build رندر می‌کند و به‌شکل فایل ایستا تحویل می‌دهد.
  • swr عدد یا boolean می‌گیرد. هدرهای کش اضافه می‌کند و پاسخ را برای آن TTL روی سرور یا reverse proxy کش می‌کند. پس از انقضا، پاسخ کهنه تحویل داده می‌شود و صفحه در پس‌زمینه بازسازی می‌شود. true عبارت stale-while-revalidate را بدون max-age اضافه می‌کند. روی preset با نام node-server در Nitro، خود Nitro می‌تواند کل پاسخ را کش کند.
  • isr مثل swr است، ولی در پلتفرم‌های پشتیبان پاسخ را در CDN هم ذخیره می‌کند؛ مستندات Netlify و Vercel را نام می‌برند. با true محتوا تا استقرار بعدی در CDN می‌ماند.
  • ssr: false بخش را فقط در مرورگر رندر می‌کند.
  • headers، redirect، cors و noScripts بقیه را پوشش می‌دهند.

مستندات Nitro اضافه می‌کند که وقتی چند قاعده تطبیق دارند، از کم‌اختصاصی به پراختصاصی ادغام می‌شوند و false قاعدهٔ ارث‌رسیده از الگوی کلی‌تر را خاموش می‌کند. پس استثنا گذاشتن ارزان است:

nuxt.config.ts
export default defineNuxtConfig({
    routeRules: {
        "/blog/**": { swr: 600 },
        // The draft preview must never be cached, even though /blog/** is
        "/blog/preview/**": { swr: false },
    },
});

swr میان‌بر cache است

طبق مستندات Nitro، swr: true برابر cache: { swr: true } است و swr: 600 برابر cache: { swr: true, maxAge: 600 }. وقتی بیش از TTL لازم دارید، از گزینهٔ cache با شیء کامل تنظیمات استفاده کنید؛ هندلرهای منطبق را با defineCachedHandler می‌پوشاند. فقط برای swr مستند شده که false قاعدهٔ کش ارث‌رسیده را بازنشانی می‌کند؛ برای cache مستندات می‌گویند false کش را غیرفعال می‌کند.

چرا ssr: false بیش از یک سوئیچ زمان اجراست

مسیری که زیر ssr: false است فقط در مرورگر رندر می‌شود، پس Nuxt می‌تواند کامپوننت صفحه را از باندل سرور کنار بگذارد، به‌شرطی که قاعده‌های زمان build همهٔ مسیرهای رسیده به آن صفحه را بپوشانند، از جمله مسیرهای پویا مثل pages/products/[id].vue زیر /products/**. مستندات می‌گویند چه چیزهایی این را خنثی می‌کند:

  • قاعده‌ای اختصاصی‌تر که SSR را زیر یک قاعدهٔ کلاینت‌محور دوباره روشن کند.
  • فرزندی با alias یا مسیر مطلق که بیرون از ناحیهٔ کلاینت‌محور بیفتد.
  • پوستهٔ والدی که فرزندی را رندر کند که هنوز سمت سرور رندر می‌شود.

مستندات تأکید می‌کنند این فقط بهینه‌سازی زمان build است و آنچه سرور به مرورگر می‌فرستد را تغییر نمی‌دهد. اگر بخش مدیریت شما یک اپ سنگین کلاینت‌محور است، یک خط "/admin/**": { ssr: false } کدش را از باندل سرور بیرون نگه می‌دارد، به شرط آنکه در آن حفره ایجاد نکنید.

بده‌بستان‌ها و نکته‌های احتیاطی

با nuxt generate کار نمی‌کند. مستندات صریحاً می‌گویند رندر ترکیبی با nuxt generate در دسترس نیست. اگر مقصد شما میزبان ایستایی است که از generate تغذیه می‌شود، swr و isr اثری ندارند؛ فقط prerender یا هیچ.

isr به پلتفرم وابسته است. مستندات Nuxt برای ذخیرهٔ CDN از Netlify و Vercel نام می‌برند. مستندات Nitro isr را بازتولید افزایشی ایستا روی Vercel توصیف می‌کنند، با شکل شیءای که expiration، allowQuery و group می‌پذیرد. اگر روی Node خودمیزبان هستید از swr استفاده کنید: روی سرور یا reverse proxy کش می‌کند نه در CDN، پس با چند نمونه پشت load balancer، کش جدا برای هر نمونه را انتظار داشته باشید مگر ذخیره‌سازی مشترک تنظیم کنید. پیش از تکیه بر isr در هر جای دیگر، رفتار preset خود را بررسی کنید.

prerender و isr را ترکیب نکنید. مستندات Nitro می‌گویند prerender اولویت دارد و isr نادیده گرفته می‌شود.

فایل‌های payload. مسیرهایی که از isr یا swr استفاده می‌کنند فایل‌های _payload.json هم تولید می‌کنند که ناوبری سمت کلاینت به‌جای واکشی دوباره می‌خواند. برای سرعت ناوبری خوب است، اما یادتان باشد این payloadها هم کش می‌شوند: داده‌های شخصی‌شده در useAsyncData روی مسیر کش‌شده در یک مدخل کش مشترک می‌نشینند. داده‌های هر کاربر را از مسیرهای swr دور نگه دارید یا سمت کلاینت واکشی کنید.

CORS و دارایی‌های ایستا. مستندات Nitro می‌گویند cors را سرورِ در حال اجرا اعمال می‌کند، نه پیکربندی CDN. در پلتفرم‌های edge که دارایی‌های prerender‌شده را سرو می‌کنند، هدرهای CORS فقط وقتی دیده می‌شوند که درخواست واقعاً به هندلر سرور برسد.

قاعده‌های مخصوص متد. کلیدی مثل "POST /api/**" فقط برای همان متد اعمال می‌شود، ولی فایل‌های بومی پلتفرم مانند _headers و _redirects نمی‌توانند بر اساس متد تفکیک کنند؛ برای هر چیزی که انتظار دارید پلتفرم تولید کند از کلیدهای بدون متد استفاده کنید.

برچسب آزمایشی. مرجع پیکربندی Nuxt مقدار routeRules سطح بالا را آزمایشی علامت زده و می‌گوید API ممکن است تغییر کند. نسخهٔ Nuxt را ثابت کنید و هنگام ارتقا مستندات را دوباره بخوانید.

قاعده از طریق محیط. مستندات Nitro از runtimeConfig.nitro.routeRules یاد می‌کنند برای بازنویسی قاعده‌ها با متغیرهای محیطی بدون build دوباره؛ مثلاً برای خاموش کردن TTL در staging.

کجا کش در سطح هندلر هنوز درست است

اگر کلید کش به داده‌های درخواست وابسته است (شناسهٔ کاربر، پارامتر query، tenant)، الگوی مسیر نمی‌تواند آن را بیان کند. مستقیماً در هندلر از API کش Nitro استفاده کنید (گزینه‌های کلید را برای نسخهٔ خود در راهنمای کش Nitro ببینید) و routeRules را برای سیاست درشت و مسیرمحور نگه دارید. این دو با هم کار می‌کنند: قاعده‌ها شکل سایت را تعیین می‌کنند، هندلرها کلیدها را.

منابع