دریافت داده بخش اصلی هر اپلیکیشنی به شمار میآید. در این مقاله قصد داریم تا نحوه 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در یک سرور کامپوننت با تایپ اسکریپت، باید از نسخه 5.1.3 یا بالاتر تایپ اسکریپت و نسخه 18.2.8 یا بالاتر @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 مسیر و تابع cacheReact پیکربندی نماییم.
اینکه دادهها در حافظه cache ذخیره میشوند یا نه به این موضوع بستگی دارد که Segment مسیر به صورت استاتیک رندر شده است یا به صورت پویا. اگر Segment استاتیک (پیشفرض) باشد، خروجی درخواست بهعنوان بخشی از Segment مسیر در حافظه cache ذخیره شده و revalidate میشود. اگر Segment پویا باشد، خروجی درخواست در حافظه cache ذخیره نمیشود و در هر درخواستی که Segment رندر میشود، دوباره fetch میگردد.
همچنین میتوانیم از API آزمایشی unstable_cacheاستفاده کنیم.
به عنوان مثال:
cacheReact برای به خاطر سپردن درخواستهای داده، مورد استفاده قرار میگیرد.revalidateدر بخش Layout و Page روی 3600تنظیم شده است، به این معنی که دادهها حداکثر هر یک ساعت یک بار در حافظه 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 در کلاینت کامپوننتها توصیه نمیشود و ممکن است باعث ایجاد رندرهای مجدد شود.
دیدگاهها: