بررسی Caching در Next.js

Next.js یک فریمورک شگفت‌انگیز است که نوشتن برنامه‌های React که رندر سمت سرور پیچیده دارند را بسیار آسان‌تر می‌کند، اما یک مشکل بزرگ وجود دارد. مکانیسم caching در Next.js بسیار پیچیده است و به راحتی می‌تواند منجر به ایجاد اشکالاتی در کد ما شود که دیباگ کردن و رفع آن‌ها دشوار است.

اگر نمی‌دانیم مکانیسم caching در Next.js چگونه کار می‌کند، به‌نظر می‌رسد که به‌جای بهره‌مندی از مزایای شگفت‌انگیز ذخیره‌سازی قدرتمند Next.js، دائماً در حال مبارزه با آن هستیم. به همین دلیل در این مقاله قصد داریم تا نحوه عملکرد هر بخش از caching در Next.js را بررسی کنیم تا بتوانیم به راحتی از عملکرد باورنکردنی آن بهره ببریم.

در طول مقاله ممکن است با دو عبارت Build Time و Request Time روبه‌رو شویم. برای اطمینان از اینکه این موضوع باعث سردرگمی در کل مقاله نمی‌شود، اجازه دهید قبل از حرکت به جلو، آن‌ها با هم بررسی کنیم.

Build Time به زمانی اشاره دارد که یک اپلیکیشن ساخته و اجرا می‌شود. هر چیزی که در طول این فرآیند cache می‌شود (بیشتر محتوای ثابت) بخشی از حافظه مربوط به Build Time خواهد بود و تنها زمانی به روز رسانی می‌شود که برنامه rebuilt و مجدداً deploy شود.

Request Time به زمانی اشاره دارد که کاربر صفحه‌ای را درخواست می‌کند. به طور معمول، داده‌هایی که در Request Time ذخیره می‌شوند پویا هستند، زیرا ما می‌خواهیم آن‌ها را مستقیماً از منبع داده در زمانی که کاربر درخواست می‌کند fetch کنیم.

مکانیسم‌های caching در Next.js

درک مفهوم caching در Next.js در ابتدا می‌تواند کمی پیچیده به نظر برسد. زیرا که از چهار مکانیسم caching متمایز تشکیل شده است که هر کدام در مراحل مختلف برنامه ما عمل می‌کنند و به گونه‌ای تعامل دارند که در ابتدا پیچیده به نظر می‌رسد.

در ادامه چهار مکانیسم caching در Next.js را داریم:

  • Request Memoization
  • Data Cache
  • Full Route Cache
  • Router Cache

ما برای هر یک از موارد بالا نقش‌های خاص آن‌ها، جایی که ذخیره می‌شوند، مدت زمان آن‌ها، نحوه مدیریت مؤثر آن‌ها، راه‌هایی برای باطل کردن cache و انصراف را بررسی خواهیم کرد.

در پایان این مقاله، درک کاملی از نحوه کار مکانیسم‌های caching برای بهینه‌سازی عملکرد Next.js خواهیم داشت.

Request Memoization

یکی از مشکلات رایج در React زمانی است که ما نیاز داریم تا اطلاعات یکسانی را در چندین مکان در یک صفحه نمایش دهیم. ساده‌ترین گزینه این است که داده‌ها را در هر دو مکان مورد نیاز fetch کنیم. اما این کار ایده‌آل نیست زیرا اکنون دو درخواست برای دریافت داده‌های مشابه از سرور خود داریم. اینجاست که Request Memoization وارد می‌شود.

Request Memoization یک ویژگی React است که هر درخواستfetchرا که در طول چرخه، در یک سرور کامپوننت رندر می‌کنیم( به طوری کلی فقط به فرآیند رندر کردن همه کامپوننت‌های یک صفحه اشاره دارد) در حافظه cache نگه می‌دارد. این بدان معناست که اگر ما یک درخواستfetch را در یک کامپوننت انجام دهیم و سپس همان درخواستfetch را در کامپوننت دیگر انجام دهیم، درخواستfetchدوم در واقع درخواستی از سرور نخواهد داشت. در عوض، از مقدار cache شده از اولین درخواست fetchاستفاده خواهد کرد.

export default async function fetchUserData(userId) {
  // The `fetch` function is automatically cached by Next.js
  const res = await fetch(`https://api.example.com/users/${userId}`)
  return res.json();
}

export default async function Page({ params }) {
  const user = await fetchUserData(params.id)

  return <>
    <h1>{user.name}</h1>
    <UserDetails id={params.id} />
  </>
}

async function UserDetails({ id }) {
  const user = await fetchUserData(id)
  return <p>{user.name}</p>
}

در کد بالا دو کامپوننت داریم: Pageو UserDetails. اولین فراخوانی تابع fetchUserData()در Pageیک درخواست fetchدرست مانند حالت عادی ایجاد می‌کند، اما مقدار بازگشتی آن درخواستfetchدر حافظه کش Request Memoization ذخیره می‌شود. دومین باری که fetchUserDataتوسط کامپوننت UserDetailsفراخوانی می‌شود، در واقع درخواست fetch جدیدی ایجاد نمی‌کند. در عوض، از مقدار ذخیره شده از اولین باری که این درخواست fetchانجام شد استفاده می‌کند. این بهینه‌سازی کوچک با کاهش تعداد درخواست‌های ارسال شده به سرور، عملکرد برنامه ما را به شدت افزایش می‌دهد. همچنین نوشتن کامپوننت‌ها را آسان‌تر می‌کند زیرا در این صورت نیازی به نگرانی در مورد بهینه‌سازی درخواست‌هایfetch نداریم.

مهم است بدانیم که این cache به طور کامل روی سرور ذخیره می‌شود، به این معنی که فقط درخواست‌های fetch ارسال شده از سرور کامپوننت‌ها را در حافظه cache ذخیره می‌کند. همچنین، این cache در شروع هر درخواست به طور کامل پاک می‌شود، یعنی این که فقط برای مدت زمان یک چرخه رندر معتبر است. اما این موضوع یک مشکل نیست، زیرا تمام هدف این cache کاهش درخواست‌هایfetch تکراری در یک چرخه رندر می‌باشد.

در نهایت، این cache فقط درخواست‌های fetch که با متد GETانجام شده‌اند را در حافظه cache ذخیره می‌کند. یک درخواست fetch نیز باید دقیقاً همان پارامترها (URL و options) را داشته باشد تا بتواند به خاطر سپرده شود.

ذخیره‌سازی درخواست‌های Non-fetch

به طور پیش‌فرض React فقط درخواست‌های fetchرا در حافظه cache ذخیره می‌کند، اما مواقعی وجود دارد که ممکن است بخواهیم انواع دیگر درخواست‌ها مانند درخواست‌های پایگاه داده را در حافظه cache ذخیره کنیم. برای این کار می‌توانیم از تابع cacheReact استفاده نماییم. تنها کاری که باید انجام دهیم این است که تابعی را که می‌خواهیم ذخیره کنیم بهcacheمنتقل کرده و یک نسخه memoized شده از آن تابع را return کنیم.

import { cache } from "react"
import { queryDatabase } from "./databaseClient"

export const fetchUserData = cache(userId => {
  // Direct database query
  return queryDatabase("SELECT * FROM users WHERE id = ?", [userId])
})

در این کد بالا، اولین باری که fetchUserData()فراخوانی می‌شود، مستقیماً در پایگاه داده پرس و جو می‌کند، زیرا هنوز هیچ نتیجه cache وجود ندارد. اما دفعه بعد که این تابع با همان userIdفراخوانی می‌شود، داده‌ها را از cache بازیابی می‌کند. درست مانند fetch، این memoization فقط برای مدت یک render pass معتبر است و شبیه به fetchmemoization عمل می‌کند.

Revalidation

Revalidation فرآیند پاک کردن cache و به روز رسانی آن با داده‌های جدید است. انجام این کار بسیار مهم می‌باشد زیرا اگر یک cache را هرگز به روز رسانی نکنیم، در نهایت داده‌های آن قدیمی می‌شود. خوشبختانه، با وجود Request Memoization نیاز نیست درمورد این موضوع نگران باشیم. زیرا، این cache فقط برای مدت یک درخواست معتبر است و نیازی نیست ما آن را revalidate کنیم.

Opting out

برای انصراف از این cache، می‌توانیم یک signalAbortControllerرا به عنوان پارامتر به درخواست fetchارسال کنیم.

async function fetchUserData(userId) {
  const { signal } = new AbortController()
  const res = await fetch(`https://api.example.com/users/${userId}`, {
    signal,
  })
  return res.json()
}

انجام این کار به React می‌گوید که این درخواست fetchرا در حافظه کش Request Memoization ذخیره نکند، اما انجام این کار توصیه نمی‌شود مگر اینکه دلیل خوبی برای آن داشته باشیم. زیرا این کش بسیار مفید است و می‌تواند عملکرد برنامه ما را به شدت بهبود بخشد.

نکته‌ای که باید با آن توجه کنیم این است که Request Memoization از نظر فنی یک ویژگی React است و منحصر به Next.js نیست. ما در این مقاله آن را به عنوان بخشی از مکانیسم‌های caching در Next.js در نظر گرفته‌ایم، زیرا درک آن برای درک کامل فرآیند caching در Next.js ضروری است.

Data Cache

Request Memoization با جلوگیری از درخواست fetchتکراری برای عملکرد بهتر برنامه ما عالی است، اما وقتی نوبت به کش کردن داده‌ها در بین درخواست‌ها یا کاربران می‌رسد بی‌فایده است. این  قسمت همان جایی است که Data Cache وارد می‌شود. این آخرین کشی است که Next.js قبل از اینکه واقعاً داده‌های ما را از یک API یا پایگاه داده fetch کند، انجام می‌دهد و در چندین درخواست و یا کاربر پایدار است.

تصور کنید ما یک صفحه ساده داریم که از یک API درخواست می‌کند تا داده‌های راهنمای یک یک شهر خاص را دریافت نماید.

export default async function Page({ params }) {
  const city = params.city
  const res = await fetch(`https://api.globetrotter.com/guides/${city}`)
  const guideData = await res.json()

  return (
    <div>
      <h1>{guideData.title}</h1>
      <p>{guideData.content}</p>
      {/* Render the guide data */}
    </div>
  )
}

این داده‌های راهنما هرگز تغییر نمی‌کنند. بنابراین منطقی نیست هر زمان که کسی به آن نیاز داشت همان لحظه fetchکنیم. در عوض باید آن داده‌ها را در تمام درخواست‌ها ذخیره کنیم تا فوراً برای کاربران آینده لود شود. به طور معمول، پیاده‌سازی این کار مشکل خواهد بود. اما خوشبختانه Next.js این کار را به شکل خودکار با Data Cache برای ما انجام می‌دهد.

به‌طور پیش‌فرض، هر درخواست fetchدر سرور کامپوننت ما در Data Cache، که در سرور ذخیره می‌شود، ذخیره می‌گردد و برای همه درخواست‌های بعدی مورد استفاده قرار می‌گیرد. این بدان معناست که اگر ۱۰۰ کاربر داشته باشیم که همگی یک داده را درخواست می‌کنند، Next.js فقط یک درخواست fetch به API میفرستد و سپس از آن داده‌های ذخیره شده برای همه ۱۰۰ کاربر استفاده می‌کند. این کار یک افزایش عملکرد بزرگ به حساب می‌آید.

Duration

Data Cache با حافظه کش Request Memoization متفاوت است، زیرا داده‌های این حافظه هرگز پاک نمی‌شوند، مگر اینکه ما به طور خاص به Next.js بگوییم این کار را انجام دهد. این داده‌ها حتی در سراسر deploymentها باقی می‌مانند. به این معنی که اگر نسخه جدیدی از برنامه خود را اجرا کنیم، Data Cache پاک نخواهد شد.

Revalidation

از آنجایی که Data Cache هرگز توسط Next.js پاک نمی‌شود، ما به روشی برای انتخاب revalidation نیاز داریم که فقط فرآیند حذف داده‌ها از کش را دربر می‌گیرد. در Next.js دو روش مختلف برای انجام این کار وجود دارد: اعتبار سنجی مجدد مبتنی بر زمان و اعتبار سنجی مجدد مبتنی بر تقاضا.

اعتبار سنجی مجدد مبتنی بر زمان

ساده‌ترین راه برای تأیید اعتبار Data Cache، پاک کردن خودکار کش پس از یک دوره زمانی مشخص است. این کار می‌تواند با دو روش انجام شود.

const res = fetch(`https://api.globetrotter.com/guides/${city}`, {
  next: { revalidate: 3600 },
})

راه اول این است که گزینه next.revalidateرا به درخواست fetchخود اضافه کنیم. این گزینه به Next.js می‌گوید که چند ثانیه باید داده‌های ما را قبل از اینکه به عنوان داده قدیمی در نظر گرفته شوند، در کش نگه دارد. در مثال بالا، به Next.js می‌گوییم که هر یک ساعت یک بار حافظه cache را مجدداً تأیید کند.

راه دیگر برای تنظیم زمان اعتبار سنجی مجدد استفاده از گزینه revalidateبخش پیکربندی است.

export const revalidate = 3600

export default async function Page({ params }) {
  const city = params.city
  const res = await fetch(`https://api.globetrotter.com/guides/${city}`)
  const guideData = await res.json()

  return (
    <div>
      <h1>{guideData.title}</h1>
      <p>{guideData.content}</p>
      {/* Render the guide data */}
    </div>
  )
}

انجام این کار باعث می‌شود همه درخواست‌های fetchبرای این صفحه، هر ساعت مجدداً تأیید شوند. مگر اینکه زمان اعتبارسنجی مجدد را روی مقدار خاص مورد نظر خود را تنظیم کرده باشیم.

یکی از مهم‌ترین چیزهایی که باید با اعتبارسنجی مجدد مبتنی بر زمان درک کنیم این است که چگونه داده‌های قدیمی را مدیریت می‌کند.

اولین باری که درخواست fetch انجام می‌شود، داده‌ها را دریافت کرده و سپس در حافظه cache ذخیره می‌کند. هر درخواست fetch جدیدی که در مدت زمان اعتبارسنجی مجدد ۱ ساعته که ما تنظیم می‌کنیم اتفاق می‌افتد، از داده‌های ذخیره‌شده استفاده می‌کند و درخواست fetch جدید ایجاد نمی‌شود. پس از ۱ساعت، اولین درخواست fetch که انجام می‌شود همچنان داده‌های cache را برمی‌گرداند، اما درخواست fetch را نیز برای دریافت داده‌های جدید و ذخیره آن‌ها در حافظه cache اجرا می‌کند. این بدان معناست که هر درخواست fetch جدید پس از این درخواست، از داده‌های ذخیره‌شده جدید استفاده می‌کند. این الگو stale-while-revalidate نامیده می‌شود و رفتاری است که Next.js آن را به کار می‌گیرد.

اعتبار سنجی مجدد مبتنی بر تقاضا

اگر داده‌های ما بر اساس یک برنامه منظم به روز رسانی نمی‌شوند، می‌توانیم از اعتبارسنجی مجدد مبتنی بر تقاضا برای تأیید مجدد حافظه cache، تنها زمانی که داده‌های جدید در دسترس باشد استفاده کنیم. این کار زمانی مفید است که می‌خواهیم حافظه cache را باطل کنیم و داده‌های جدید را تنها زمانی که مقاله جدیدی منتشر می‌شود یا رویداد خاصی رخ می‌دهد، fetch کنیم.

این کار را می‌توانیم به یکی از دو روش زیر انجام دهیم.

import { revalidatePath } from "next/cache"

export async function publishArticle({ city }) {
  createArticle(city)

  revalidatePath(`/guides/${city}`)
}

تابع revalidatePathیک مسیر رشته‌ای را دریافت می‌کند و کش تمام درخواست‌های fetchدر آن مسیر را پاک می‌کند.

اگر بخواهیم در درخواست‌های fetch برای اعتبارسنجی مجدد دقیق‌تر عمل کنیم، می‌توانیم از تابع revalidateTagاستفاده کنیم.

const res = fetch(`https://api.globetrotter.com/guides/${city}`, {
  next: { tags: ["city-guides"] },
})

در اینجا، ما تگ city-guidesرا به درخواست fetchخود اضافه می‌کنیم تا بتوانیم آن را با revalidateTagمورد هدف قرار دهیم.

import { revalidateTag } from "next/cache"

export async function publishArticle({ city }) {
  createArticle(city)

  revalidateTag("city-guides")
}

هنگامی کهrevalidateTagرا با یک رشته فراخوانی می‌کنیم، کش تمام درخواست‌های fetchبا آن تگ را پاک می‌کند.

Opting out

انصراف از Data Cache را می‌توانیم به روش‌های مختلفی انجام دهیم.

no-store
const res = fetch(`https://api.globetrotter.com/guides/${city}`, {
  cache: "no-store",
})

با ارسال cache: "no-store"به درخواست fetch، به Next.js می‌گوییم که این درخواست را در Data Cache ذخیره نکند. این کار زمانی مفید است که ما داده‌هایی داریم که دائماً در حال تغییر هستند و می‌خواهیم هر بار آن‌ها را به روز دریافت نماییم.

همچنین می‌توانیم تابع noStoreرا فراخوانی کنیم تا از Data Cache برای هر چیزی که در scope آن تابع است انصراف دهیم.

import { unstable_noStore as noStore } from "next/cache"

function getGuide() {
  noStore()
  const res = fetch(`https://api.globetrotter.com/guides/${city}`)
}

نکته‌ای که باید به آن توجه داشته باشیم این است که در حال حاضر، این یک ویژگی آزمایشی بوده و به همین دلیل پیشوند آن با unstable_ می‌باشد، اما در آینده، این روش به عنوان ترجیحی برای انصراف از Data Cache در Next.js استفاده می‌شود.

این یک راه واقعا عالی برای انصراف از کش کردن بر اساس هر کامپوننت یا هر تابع است، زیرا سایر روش‌های انصراف از Data Cache کل صفحه را تحت تاثیر قرار می‌دهند.

export const dynamic = 'force-dynamic'

اگر بخواهیم رفتار کش را برای کل صفحه و نه فقط یک درخواست fetchخاص تغییر دهیم، می‌توانیم این گزینه پیکربندی بخش را به سطح بالای فایل خود اضافه کنیم. این باعث می‌شود تا صفحه پویا باشد و به طور کامل از Data Cache انصراف دهد.

export const dynamic = "force-dynamic"
export const revalidate = 0

راه دیگر برای حذف کل صفحه از Data Cache، استفاده از گزینه revalidateبخش پیکربندی با مقدار ۰ است.

export const revalidate = 0

این خط تقریباً معادل cache: "no-store"در سطح صفحه است. این گزینه برای همه درخواست‌های موجود در صفحه اعمال می‌شود و اطمینان حاصل می‌کند که هیچ چیز کش نمی‌شود.

ذخیره‌سازی درخواست‌های Non-fetch

تا کنون، ما فقط نحوه ذخیره کردن درخواست‌های fetchبا Data Cache را بررسی کرده‌ایم. اما می‌توانیم کارهای بیشتری انجام دهیم.

اگر به مثال قبلی خود درمورد راهنماهای شهری برگردیم، ممکن است بخواهیم داده‌ها را مستقیماً از پایگاه داده خود استخراج کنیم. برای این کار، می‌توانیم از تابع cacheکه توسط Next.js ارائه شده است استفاده کنیم. این تابع شبیه به تابع cache React است، با این تفاوت که به جای Request Memoization، در Data Cache اعمال می‌شود.

import { getGuides } from "./data"
import { cache as unstable_cache } from "next/cache"

const getCachedGuides = cache(city => getGuides(city), ["guides-cache-key"])

export default async function Page({ params }) {
  const guides = await getCachedGuides(params.city)
  // ...
}

البته باید به این نکته توجه داشته باشیم که در حال حاضر، این یک ویژگی آزمایشی بوده و به همین دلیل پیشوند آن با unstable_ می‌باشد. اما این تنها راه برای ذخیره‌سازی درخواست‌های non-fetch در Data Cache است.

تابع cache سه پارامتر می‌گیرد (اما فقط دو پارامتر مورد نیاز است). اولین پارامتر تابعی است که می‌خواهیم کش کنیم. در مثالی که ما داریم تابعgetGuidesاست. پارامتر دوم کلید حافظه کش است. زیرا Next.js برای شناسایی کش‌ها به یک کلید نیاز دارد. این کلید آرایه‌ای از رشته‌ها است و باید برای هر کش منحصر به فردی که داریم ،یکتا باشد. اگر دو تابع cacheدارای آرایه کلیدی یکسانی باشند که به آن‌ها ارسال شده است، همان درخواست دقیق در نظر گرفته می‌شوند و در کش یکسان ذخیره می‌گردند(مشابه درخواست fetch با URL و پارامترهای یکسان).

سومین پارامتر، یک پارامتر گزینه‌ای اختیاری است که در آن می‌توانیم مواردی مانند زمان اعتبار سنجی مجدد و برچسب‌ها را تعریف کنیم.

در کدی که ما داریم، نتایج تابعgetGuidesرا کش کرده و با کلید ["guides-cache-key"]در حافظه کش ذخیره می‌کنیم. این بدان معناست که اگر دو بار getCachedGuidesرا با همان شهر فراخوانی کنیم، بار دوم به جای فراخوانی مجدد getGuides، از داده‌های ذخیره شده استفاده می‌کند.

Full Route Cache

سومین نوع کش، Full Route Cache است. درک این مورد کمی ساده‌تر از مفاهیم قبلی است، زیرا نسبت به Data Cache تنظیمات و پیکر‌بندی کم‌تری دارد.اصلی‌ترین دلیل مفید بودن این Cache این است که به Next.js اجازه می‌دهد تا به جای اینکه صفحات استاتیک را برای هر درخواست بسازد، آن‌ها را در زمان ساخت(built time) ذخیره کند.

در Next.js، صفحاتی که ما به کلاینت‌های خود ارائه می‌کنیم از HTML و چیزی به نام React Server Component Payload (RSCP) تشکیل شده است. payload شامل دستورالعمل‌هایی برای نحوه کارکرد کلاینت کامپوننت‌ها با سرور کامپوننت‌های رندر شده با هدف رندر صفحه می‌باشد. کاری که Full Route Cache انجام می‌دهد این است که HTML و RSCP را برای صفحات استاتیک در زمان ساخت(built time) ذخیره می‌کند.

به عنوان مثال:

import Link from "next/link"

async function getBlogList() {
  const blogPosts = await fetch("https://api.example.com/posts")
  return await blogPosts.json()
}

export default async function Page() {
  const blogData = await getBlogList()

  return (
    <div>
      <h1>Blog Posts</h1>
      <ul>
        {blogData.map(post => (
          <li key={post.slug}>
            <Link href={`/blog/${post.slug}`}>
              <a>{post.title}</a>
            </Link>
            <p>{post.excerpt}</p>
          </li>
        ))}
      </ul>
    </div>
  )
}

کدی که در بالا داریم، Pageدر زمان زمان ساخت(built time) کش می‌شود زیرا حاوی هیچ داده پویایی نیست. به طور خاص، HTML و RSCP آن در Full Route Cache ذخیره می‌شود تا زمانی که کاربر درخواست دسترسی می‌کند، سریع‌تر رندر شود. تنها کاری که باعث می‌شود این HTML و یا RSCP به روز رسانی شود این است که ما برنامه خود را مجدداً deploy کنیم یا این که data cache را، که این صفحه به آن وابسته است به صورت دستی باطل نماییم.

ممکن است اینطور به نظر برسد از آنجایی که ما در حال انجام یک درخواست fetch هستیم داده‌های پویا داریم، اما این درخواست fetch توسط Next.js در Data Cache ذخیره می‌شود، بنابراین این صفحه در واقع استاتیک در نظر گرفته می‌شود. داده‌های پویا داده‌هایی هستند که در هر درخواست به یک صفحه تغییر می‌کنند، مانند پارامتر URL پویا، کوکی‌ها، هدرها، پارامترهای جستجو و غیره.

Full Route Cache مانند Data Cache، در سرور ذخیره می‌شود و در درخواست‌ها و کاربران مختلف باقی می‌ماند، اما برخلاف Data Cache، هر بار که برنامه خود را مجدداً deploy می‌کنیم، این کش پاک می‌شود.

Opting out

انصراف از Full Route Cache به دو صورت انجام می‌شود.

راه اول انصراف از Data Cache است. اگر داده‌هایی که برای صفحه fetch می‌کنیم در Data Cache ذخیره نشده باشد، در Full Route Cache نیز از آن‌ها استفاده نخواهد شد.

راه دوم این است که از داده‌های پویا در صفحه خود استفاده کنیم. داده‌های پویا شامل مواردی مانند headers، cookiesیا توابع پویا searchParamsو پارامترهای URL پویا مانند idدر /blog/[id]هستند.

Router Cache

این Cache آخر کمی منحصر به فرد است زیرا تنها Cacheای می‌باشد که به جای سرور بر روی کلاینت ذخیره می‌شود. همچنین اگر به درستی درک نشود، می‌تواند منبع بسیاری از باگ‌ها باشد. Router Cache مسیرهایی که کاربر از آن‌ها بازدید می‌کند را ذخیره می‌کند. بنابراین وقتی کاربر به آن مسیرها بازمی‌گردد، از نسخه ذخیره‌شده استفاده می‌کند و در واقع هیچ درخواستی از سرور صورت نمی‌گیرد. این روش در مورد سرعت لود صفحه یک مزیت است، اما می‌تواند مشکلاتی را هم ایجاد کند که در ادامه به آن می‌پردازیم.

export default async function Page() {
  const blogData = await getBlogList()

  return (
    <div>
      <h1>Blog Posts</h1>
      <ul>
        {blogData.map(post => (
          <li key={post.slug}>
            <Link href={`/blog/${post.slug}`}>
              <a>{post.title}</a>
            </Link>
            <p>{post.excerpt}</p>
          </li>
        ))}
      </ul>
    </div>
  )
}

در کدی که داریم، وقتی کاربر به این صفحه می‌رود، HTML و RSCP آن در Router Cache ذخیره می‌شود. به طور مشابه، هنگامی که کاربر به هر یک از مسیرهای /blog/${post.slug}می‌رود، آن HTML و RSCP نیز در cache ذخیره می‌شود. این بدان معناست که اگر کاربر به صفحه‌ای که قبلاً در آن بوده است بازگردد، به جای درخواست از سرور، آن HTML و RSCP را از Router Cache درخواست می‌کند.

Duration

router cache کمی منحصر به فرد است زیرا مدت زمان ذخیره آن به نوع مسیر بستگی دارد. برای مسیرهای استاتیک، کش به مدت ۵ دقیقه ذخیره می‌شود. اما برای مسیرهای پویا، کش تنها ۳۰ ثانیه ذخیره می‌گردد. این بدان معنی است که اگر کاربر به یک مسیر استاتیک برود و سپس در عرض ۵ دقیقه به آن بازگردد، از نسخه کش استفاده خواهد کرد. اما اگر بعد از ۵ دقیقه دوباره به آن بازگردد، درخواستی از سرور برای دریافت HTML و RSCP جدید صورت می‌گیرد. همین مورد در مورد مسیرهای پویا نیز صدق می‌کند، با این تفاوت که cache به جای ۵ دقیقه فقط ۳۰ ثانیه ذخیره می‌شود.

این کش همچنین فقط برای session فعلی کاربر ذخیره می‌شود. به این معنی که اگر کاربر tab را ببندد یا صفحه را رفرش کند، کش پاک می‌شود.

همچنین می‌توانیم با پاک کردن data cache از server action با استفاده از revalidatePath/revalidateTag، این cache را مجدداً به‌صورت دستی تأیید کنیم. همینطور می‌توانیم تابع router.refreshرا که از هوک useRouterروی کلاینت دریافت می‌کنیم، فراخوانی نماییم. این کار کلاینت را مجبور می‌کند تا صفحه‌ای را که در حال حاضر در آن هستیم را دوباره fetch کند.

Revalidation

در بخش قبل دو روش اعتبار سنجی مجدد را مورد بحث قرار دادیم، اما راه‌های زیادی برای انجام آن وجود دارد.

ما می‌توانیم مشابه روشی که این کار را برای Data Cache انجام دادیم، Router Cache را در صورت تقاضا مجدداً اعتبارسنجی کنیم. یعنی این که اعتبار سنجی مجدد Data Cache با استفاده از revalidatePathیا revalidateTagنیز باعث اعتبارسنجی مجدد Router Cache می‌شود.

Opting out

هیچ راهی برای انصراف از Router Cache وجود ندارد، اما با توجه به روش‌های فراوانی که برای اعتبارسنجی مجدد کش وجود دارد، مشکل چندانی نیست.

جمع‌بندی

در این مقاله سعی کردیم تا با انواع مکانیسم‌های caching در Next.js آشنا شویم و با درک بهتر رفتار آن‌ها بتوانیم تنظیماتی که برای عملکرد بهتر برنامه لازم است را پیکربندی کنیم.

دیدگاه‌ها:

Faezeeh_rb

بهمن 12, 1402  در  2:18 ب.ظ

فرشید انجیلی

بهمن 3, 1402  در  2:28 ب.ظ

بسیار عالی و جامع بود , ممنون

افزودن دیدگاه جدید