دریافت داده بخش اصلی هر اپلیکیشنی به شمار می‌آید. در این مقاله قصد داریم تا نحوه fetch، cache و revalidate کردن داده در React و Next.js را باهم بررسی کنیم.

چهار روش در Next.js برای دریافت کردن داده وجود دارد که عبارتند از:

دریافت داده بر روی سرور با استفاده از fetch در 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>
}

بهتر است به این نکات توجه داشته باشیم که:

Caching داده‌ها

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 استفاده می‌کند، در این صورت ذخیره نمی‌شود.

Revalidating داده‌ها

Revalidation در Next.js فرآیند پاکسازی حافظه cache و دریافت کردن مجدد آخرین داده ها می‌باشد. این موضوع زمانی مفید است که داده‌ها تغییر می‌کند و ما می‌خواهیم مطمئن شویم که آخرین اطلاعات را به کاربران نمایش می‌دهیم.

داده‌های ذخیره شده را می‌توانیم به دو روش revalidate کنیم:

Revalidation مبتنی بر زمان

برای 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 می‌شود.

Revalidation مبتنی بر تقاضا

می‌توانیم داده‌ها را بر حسب تقاضا توسط path (revalidatePath) یا توسط tag حافظه cache (revalidateTag) در داخل یک Server Action یا Route Handler، مجددا revalidated کنیم.

Next.js یک سیستم cache tagging برای باطل کردن درخواست‌های fetchدر مسیرها دارد.

  1. هنگام استفاده از fetch، این گزینه را داریم که ورودی‌های cache را با یک یا چند تگ برچسب‌گذاری کنیم.
  2. سپس، می‌توانیم 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')
}

مدیریت خطا و  revalidation

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

انصراف از Data Caching

درخواست‌های fetchدر حافظه cache ذخیره نمی‌شوند اگر:

درخواست‌های fetch منفرد

برای انصراف از ذخیره‌سازی برای درخواست‌های fetchمنفرد، می‌توانیم گزینه cacheرا در fetch بر روی 'no-store'تنظیم کنیم. به این ترتیب داده‌ها به صورت پویا و در هر درخواست، دریافت می‌شوند.

layout.js | page.js

fetch('https://...', { cache: 'no-store' })

درخواست‌های fetch چندگانه

اگر چندین درخواست fetch در یک segment مسیر داشته باشیم (مثلاً یک Layout یا Page)، می‌توانیم رفتار ذخیره‌سازی تمام درخواست‌های داده در segment را با استفاده از گزینه‌های پیکربندی Segment پیکربندی کنیم.

با این حال، توصیه می‌شود که رفتار ذخیره‌سازی برای هر درخواست fetchرا به‌صورت جداگانه پیکربندی کنیم. این کار کمک می‌کند تا کنترل دقیق‌تری بر رفتار caching داشته باشیم.

دریافت داده بر روی سرور با استفاده از کتابخانه‌های third-party در Next.js

در مواردی که از کتابخانه third-party استفاده می‌کنیم که fetchرا پشتیبانی نمی‌کند یا در معرض نمایش قرار نمی‌دهد (به عنوان مثال، یک پایگاه داده، CMS یا ORM کلاینت)، می‌توانیم رفتار caching و revalidating آن درخواست‌ها را با استفاده از گزینه پیکربندی Segment مسیر و تابع cacheReact پیکربندی نماییم.

اینکه داده‌ها در حافظه cache ذخیره می‌شوند یا نه به این موضوع بستگی دارد که Segment مسیر به صورت استاتیک رندر شده است یا به صورت پویا. اگر Segment استاتیک (پیش‌فرض) باشد، خروجی درخواست به‌عنوان بخشی از Segment مسیر در حافظه cache ذخیره شده و revalidate می‌شود. اگر Segment پویا باشد، خروجی درخواست در حافظه cache ذخیره نمی‌شود و در هر درخواستی که Segment رندر می‌شود، دوباره fetch می‌گردد.

همچنین می‌توانیم از API آزمایشی unstable_cacheاستفاده کنیم.

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

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)
  // ...
}

دریافت داده بر روی کلاینت با استفاده از Route Handlerها در Next.js

اگر نیاز به fetch کردن داده‌ها در یک کلاینت کامپوننت داریم، می‌توانیم یک Route Handler را از کلاینت فراخوانی کنیم. Route Handlerها روی سرور اجرا می‌شوند و داده‌ها را به کلاینت retrun می‌کنند. این کار زمانی مفید است که نمی‌خواهیم اطلاعات حساسی مانند توکن‌های API را در اختیار کلاینت قرار دهیم.

نکته‌ای که باید درمورد سرور کامپوننت‌ها و Route Handlerها به آن توجه کنیم این است که سرور کامپوننت‌ها روی سرور رندر می‌شوند، از این رو برای دریافت کردن داده‌ها نیازی به فراخوانی Route Handler از یک سرور کامپوننت نداریم. در عوض، می‌توانیم داده‌ها را مستقیماً در داخل سرور کاپموننت دریافت نماییم.

دریافت داده بر روی کلاینت با استفاده از کتابخانه‌های third-party در Next.js

همچنین می‌توانیم با استفاده از یک کتابخانه third-party مانند SWR یا TanStack Query، داده‌ها را روی کلاینت دریافت کنیم. این کتابخانه‌ها برای به خاطر سپردن درخواست‌ها، caching، revalidating و mutating داده‌ها APIهای خود را ارائه می‌کنند.

درمورد APIهای آینده باید به این موضوع توجه کنیم که use یک تابع React است که promiseای را که توسط یک تابع return می‌شود را می‌پذیرد وآن را مدیریت می‌کند. در حال حاضر قرار دادن fetch در تابع use در کلاینت کامپوننت‌ها توصیه نمی‌شود و ممکن است باعث ایجاد رندرهای مجدد شود.