روش‌های دریافت داده در Next.js

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

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

  • بر روی سرور، با fetch
  • بر روی سرور، با کتابخانه‌های third-party
  • روی کلاینت، از طریق یک Route Handler
  • روی کلاینت، با کتابخانه‌های third-party

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

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

  • Next.js توابع مفیدی را ارائه می‌کند که ممکن است هنگام fetch کردن داده در سرور کامپوننت‌هایی مانند cookies و headers به آن‌ها نیاز داشته باشیم. این‌ها باعث می‌شوند تا مسیر به صورت پویا رندر شود، زیرا به اطلاعات زمان درخواست متکی هستند.
  • در Route handlerها، درخواست‌های fetchدر حافظه ذخیره نمی‌شوند، زیرا Route Handler‌ها بخشی از درخت کامپوننت React نیستند.
  • برای استفاده از async/awaitدر یک سرور کامپوننت با تایپ اسکریپت، باید از نسخه ۵٫۱٫۳ یا بالاتر تایپ اسکریپت و نسخه ۱۸٫۲٫۸ یا بالاتر @types/reactاستفاده کنیم.

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 می‌کند. استفاده از این روش برای داده‌هایی که به ندرت تغییر می‌کنند و به روز بودن آن‌ها ‌چندان مهم نیست، بسیار مفید می‌باشد.
  • Revalidation مبتنی بر تقاضا: این روش، revalidate داده‌ها را به صورت دستی و بر اساس یک رویداد (به عنوان مثال ارسال فرم) تنظیم می‌کند. Revalidation مبتنی بر تقاضا می‌تواند از رویکرد tag-based یا path-based برای 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 ذخیره نمی‌شوند اگر:

  • 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 منفرد

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

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

  • تابع cacheReact برای به خاطر سپردن درخواست‎‌های داده، مورد استفاده قرار می‌گیرد.
  • گزینه 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)
  // ...
}

دریافت داده بر روی کلاینت با استفاده از 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 در کلاینت کامپوننت‌ها توصیه نمی‌شود و ممکن است باعث ایجاد رندرهای مجدد شود.

دیدگاه‌ها:

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