دریافت داده بخش اصلی هر اپلیکیشنی به شمار میآید. در این مقاله قصد داریم تا نحوه fetch، cache و revalidate کردن داده در React و Next.js را باهم بررسی کنیم.
چهار روش در Next.js برای دریافت کردن داده وجود دارد که عبارتند از:
کاری که Next.js انجام میدهد این است که fetch
Web API native را extend میکند تا به ما اجازه دهد رفتار caching و revalidating را برای هر درخواست fetch بر روی سرور پیکربندی کنیم. کاری که React انجام میدهد این است کهfetch
را extend میکند تا حین رندر کردن درخت کامپوننت React، درخواستهای fetch به طور خودکار به خاطر سپرده شوند.
ما میتوانیم از fetch
همراه با async
/await
در سرور کامپوننتها، در Route Handlerها و همینطور در Server Actionها استفاده نماییم. به عنوان مثال:
app/page.tsx async function getData() { const res = await fetch('https://api.example.com/...') // The return value is *not* serialized // You can return Date, Map, Set, etc. if (!res.ok) { // This will activate the closest `error.js` Error Boundary throw new Error('Failed to fetch data') } return res.json() } export default async function Page() { const data = await getData() return <main></main> }
بهتر است به این نکات توجه داشته باشیم که:
cookies
و headers
به آنها نیاز داشته باشیم. اینها باعث میشوند تا مسیر به صورت پویا رندر شود، زیرا به اطلاعات زمان درخواست متکی هستند.fetch
در حافظه ذخیره نمیشوند، زیرا Route Handlerها بخشی از درخت کامپوننت React نیستند.async
/await
در یک سرور کامپوننت با تایپ اسکریپت، باید از نسخه ۵٫۱٫۳ یا بالاتر تایپ اسکریپت و نسخه ۱۸٫۲٫۸ یا بالاتر @types/react
استفاده کنیم.Caching دادهها را ذخیره میکند، بنابراین نیازی نیست در هر درخواستی برای دریافت مجدد دادهها، آنها را از منبع دادهای که داریم fetch کنیم.
بهطور پیشفرض، Next.js به صورت خودکار مقادیر بازگشتی fetch
را در حافظه cache بر روی سرور ذخیره میکند. این بدان معنی است که ما میتوانیم دادهها را در زمان ساخت یا زمان درخواست fetch کنیم، cache کنیم و در هر درخواست داده دوباره از آن استفاده نماییم.
// 'force-cache' is the default, and can be omitted fetch('https://...', { cache: 'force-cache' })
درخواستهای fetch
که از متد POST
استفاده میکنند هم بهطور خودکار ذخیره میشوند. مگر اینکه داخل یک Route Handler باشد که از متد POST
استفاده میکند، در این صورت ذخیره نمیشود.
Revalidation در Next.js فرآیند پاکسازی حافظه cache و دریافت کردن مجدد آخرین داده ها میباشد. این موضوع زمانی مفید است که دادهها تغییر میکند و ما میخواهیم مطمئن شویم که آخرین اطلاعات را به کاربران نمایش میدهیم.
دادههای ذخیره شده را میتوانیم به دو روش revalidate کنیم:
برای revalidate دادهها در یک بازه زمانی معین، میتوانیم از گزینه next.revalidate
مربوط به fetch
برای تنظیم طول عمر حافظه cache یک منبع (در ثانیه) استفاده کنیم.
fetch('https://...', { next: { revalidate: 3600 } })
از طرف دیگر، برای revalidate همه درخواستهای fetch
در یک segment مسیر، میتوانیم از گزینههای پیکربندی Segment استفاده نماییم.
layout.js | page.js export const revalidate = 3600 // revalidate at most every hour
اگر چندین درخواست fetch در یک مسیر رندر شده استاتیک داشته باشیم و هر کدام فرکانس revalidation متفاوتی داشته باشند؛ در این صورت کمترین زمان برای همه درخواستها مورد استفاده قرار میگیرد. اما برای مسیرهای رندر شده به صورت پویا، هر درخواست fetch
مستقلاً revalidate میشود.
میتوانیم دادهها را بر حسب تقاضا توسط path (revalidatePath
) یا توسط tag حافظه cache (revalidateTag
) در داخل یک Server Action یا Route Handler، مجددا revalidated کنیم.
Next.js یک سیستم cache tagging برای باطل کردن درخواستهای fetch
در مسیرها دارد.
fetch
، این گزینه را داریم که ورودیهای cache را با یک یا چند تگ برچسبگذاری کنیم.revalidateTag
را فراخوانی کنیم تا از این طریق همه ورودیهای مرتبط با آن تگ را revalidate نماییم.به عنوان مثال، درخواست fetch زیر تگ cache collectio
را اضافه میکند:
app/page.tsx export default async function Page() { const res = await fetch('https://...', { next: { tags: ['collection'] } }) const data = await res.json() // ... }
سپس میتوانیم این fetch
برچسبگذاری شده با collection
را با فراخوانی revalidateTag
در یک Server Action مجدداً revalidate کنیم:
app/actions.ts 'use server' import { revalidateTag } from 'next/cache' export default async function action() { revalidateTag('collection') }
اگر هنگام تلاش برای revalidate کردن دادهها خطایی رخ بدهد، در این صورت آخرین دادههایی که با موفقیت تولید شده بودند همچنان از حافظه cache ارائه میشوند. سپس درخواست بعدی، Next.js مجدداً دادهها را revalidate میکند.
درخواستهای fetch
در حافظه cache ذخیره نمیشوند اگر:
cache: 'no-store'
به درخواستهای fetch
اضافه شود.revalidate: 0
به درخواستهای fetch
منفرد اضافه شود.fetch
داخل یک Router Handler باشد که از متد POST
استفاده میکند.fetch
پس از استفاده از headers
یا cookies
انجام شود.const dynamic = 'force-dynamic'
در segment مسیر استفاده شود.fetchCache
در segment مسیر بهطور پیشفرض به گونهای پیکربندی شده باشد که از حافظه cache عبور کند.fetch
از هدرهای Authorization
یا Cookie
استفاده کند و یک درخواست ذخیره نشده بالای آن در درخت کامپوننت وجود داشته باشد.برای انصراف از ذخیرهسازی برای درخواستهای fetch
منفرد، میتوانیم گزینه cache
را در fetch
بر روی 'no-store'
تنظیم کنیم. به این ترتیب دادهها به صورت پویا و در هر درخواست، دریافت میشوند.
layout.js | page.js fetch('https://...', { cache: 'no-store' })
اگر چندین درخواست fetch
در یک segment مسیر داشته باشیم (مثلاً یک Layout یا Page)، میتوانیم رفتار ذخیرهسازی تمام درخواستهای داده در segment را با استفاده از گزینههای پیکربندی Segment پیکربندی کنیم.
با این حال، توصیه میشود که رفتار ذخیرهسازی برای هر درخواست fetch
را بهصورت جداگانه پیکربندی کنیم. این کار کمک میکند تا کنترل دقیقتری بر رفتار caching داشته باشیم.
در مواردی که از کتابخانه third-party استفاده میکنیم که fetch
را پشتیبانی نمیکند یا در معرض نمایش قرار نمیدهد (به عنوان مثال، یک پایگاه داده، CMS یا ORM کلاینت)، میتوانیم رفتار caching و revalidating آن درخواستها را با استفاده از گزینه پیکربندی Segment مسیر و تابع cache
React پیکربندی نماییم.
اینکه دادهها در حافظه cache ذخیره میشوند یا نه به این موضوع بستگی دارد که Segment مسیر به صورت استاتیک رندر شده است یا به صورت پویا. اگر Segment استاتیک (پیشفرض) باشد، خروجی درخواست بهعنوان بخشی از Segment مسیر در حافظه cache ذخیره شده و revalidate میشود. اگر Segment پویا باشد، خروجی درخواست در حافظه cache ذخیره نمیشود و در هر درخواستی که Segment رندر میشود، دوباره fetch میگردد.
همچنین میتوانیم از API آزمایشی unstable_cache
استفاده کنیم.
به عنوان مثال:
cache
React برای به خاطر سپردن درخواستهای داده، مورد استفاده قرار میگیرد.revalidate
در بخش Layout و Page روی ۳۶۰۰
تنظیم شده است، به این معنی که دادهها حداکثر هر یک ساعت یک بار در حافظه cache ذخیره شده و revalidate میشوند.app/utils.ts import { cache } from 'react' export const getItem = cache(async (id: string) => { const item = await db.item.findUnique({ id }) return item })
اگرچه تابع getItem
دو بار فراخوانی میشود، اما تنها یک query در پایگاه داده انجام میگردد.
app/item/[id]/layout.tsx import { getItem } from '@/utils/get-item' export const revalidate = 3600 // revalidate the data at most every hour export default async function Layout({ params: { id }, }: { params: { id: string } }) { const item = await getItem(id) // ... }
app/item/[id]/page.tsx import { getItem } from '@/utils/get-item' export const revalidate = 3600 // revalidate the data at most every hour export default async function Page({ params: { id }, }: { params: { id: string } }) { const item = await getItem(id) // ... }
اگر نیاز به fetch کردن دادهها در یک کلاینت کامپوننت داریم، میتوانیم یک Route Handler را از کلاینت فراخوانی کنیم. Route Handlerها روی سرور اجرا میشوند و دادهها را به کلاینت retrun میکنند. این کار زمانی مفید است که نمیخواهیم اطلاعات حساسی مانند توکنهای API را در اختیار کلاینت قرار دهیم.
نکتهای که باید درمورد سرور کامپوننتها و Route Handlerها به آن توجه کنیم این است که سرور کامپوننتها روی سرور رندر میشوند، از این رو برای دریافت کردن دادهها نیازی به فراخوانی Route Handler از یک سرور کامپوننت نداریم. در عوض، میتوانیم دادهها را مستقیماً در داخل سرور کاپموننت دریافت نماییم.
همچنین میتوانیم با استفاده از یک کتابخانه third-party مانند SWR یا TanStack Query، دادهها را روی کلاینت دریافت کنیم. این کتابخانهها برای به خاطر سپردن درخواستها، caching، revalidating و mutating دادهها APIهای خود را ارائه میکنند.
درمورد APIهای آینده باید به این موضوع توجه کنیم که use
یک تابع React است که promiseای را که توسط یک تابع return میشود را میپذیرد وآن را مدیریت میکند. در حال حاضر قرار دادن fetch در تابع use
در کلاینت کامپوننتها توصیه نمیشود و ممکن است باعث ایجاد رندرهای مجدد شود.
دیدگاهها: