Next.js 16 مكون من مكونات الـ Cache: شرح الـ Multi-Tenant (2026)

١ يونيو ٢٠٢٦

Next.js 16 Cache Components: Multi-Tenant Tutorial (2026)

دليل تعليمي حول مكونات التخزين المؤقت (Cache Components) في Next.js 16، يقوم بتمرير هوية المستأجر (tenant identity) عبر كل مفتاح تخزين مؤقت 'use cache'. مرر معرف المستأجر (tenant id) كوسيط صريح لينضم إلى المفتاح، وقم بوسم كل إدخال بـ tenant:{id} للإبطال الجراحي الدقيق، واختر ملف تعريف cacheLife لكل فئة اشتراك. قابل للتشغيل على Next.js 16.2.6.

ما ستتعلمه

  • لماذا يمنع Next.js استخدام cookies() / headers() / searchParams داخل 'use cache' وماذا تقول الوثائق عما يحدث للـ closures غير القابلة للتسلسل1.
  • كيفية تفعيل مكونات التخزين المؤقت في next.config.ts واستخدام 'use cache'، و cacheLife، و cacheTag معاً2.
  • كيفية اشتقاق مفتاح تخزين مؤقت لكل مستأجر من جزء المسار (route segment) ونشره عبر طبقة البيانات الخاصة بك.
  • كيفية إبطال إدخالات مستأجر واحد فقط باستخدام revalidateTag و updateTag3.
  • كيفية تحديد ملف تعريف cacheLife مخصص في next.config.ts لفئة مستأجر pro مع تحديث أكثر صرامة4.
  • كيفية التحقق من نجاح التخزين المؤقت (cache hits) ونطاق المستأجر باستخدام NEXT_PRIVATE_DEBUG_CACHE=15.

المتطلبات الأساسية

  • Node.js 24.x. إصدار Node 24 هو الـ LTS النشط حالياً حتى 20 أكتوبر 2026 ثم يدخل مرحلة صيانة LTS حتى 30 أبريل 20286. يتطلب حقل المحركات (engines) في Next.js 16 إصدار Node >=20.9.0، لكن Node 20 وصل لنهاية عمره الافتراضي في 30 أبريل 20266، لذا لا تبدأ مشروعاً جديداً عليه7.
  • pnpm 10.x أو npm 11.x. تستخدم الأمثلة pnpm؛ يمكنك التبديل لمدير الحزم الخاص بك بحرية.
  • مجلد عمل نظيف لتشغيل pnpm dlx create-next-app@16.2.6 ....
  • الإلمام بـ App Router و Server Components.

لماذا تهم مفاتيح التخزين المؤقت المخصصة للمستأجر

مكونات التخزين المؤقت هي تخزين مؤقت اختياري (opt-in). يحدد التوجيه 'use cache' دالة أو مكوناً كقابل للتخزين المؤقت؛ يقوم Next.js ببناء مفتاح تخزين مؤقت من معرف البناء (build id)، ومعرف دالة ثابت، والوسائط المتسلسلة، وأي متغيرات ملتقطة في الـ closure8. تشترك استدعاءتان بنفس الوسائط في إدخال تخزين مؤقت واحد. هذا هو بالضبط ما تريده لنسخة تسويقية مشتركة. لكنه بالضبط ما لا تريده لـ getInvoices() في تطبيق SaaS حيث يمتلك كل مستأجر دفتر حسابات خاص به.

الخطر خفي. إذا كانت دالة طبقة البيانات الخاصة بك لا تأخذ وسائط، ولا تحتوي على closure لقيمة مرتبطة بنطاق الطلب، وتقرأ معرف المستأجر من singleton على مستوى الوحدة أو قناة جانبية، فسينهار جميع المستأجرين في إدخال تخزين مؤقت واحد ويفوز الطلب الأول. يمنع Next.js صراحةً قراءة cookies()، أو headers()، أو searchParams داخل دالة 'use cache' تحديداً لأن مفتاح التخزين المؤقت لا يمكنه عكس قيمها1. الحل هو جعل هوية المستأجر وسيطاً (argument) لكل دالة مخزنة مؤقتاً، ثم اشتقاق ذلك الوسيط من مكان يمكن لـ Next.js رؤيته — وهو عنوان URL.

الخطوة 1 — إنشاء هيكل تطبيق Next.js 16 مع مكونات التخزين المؤقت

أنشئ المشروع بشكل تفاعلي:

pnpm dlx create-next-app@16.2.6 saas-cache --yes \
  --ts --app --tailwind --src-dir --eslint \
  --import-alias '@/*'
cd saas-cache

تأكد من الإصدار الذي تم تثبيته:

pnpm list next
# next 16.2.6

ثم قم بتفعيل مكونات التخزين المؤقت في next.config.ts:

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  cacheComponents: true,
}

export default nextConfig

cacheComponents هو العلم الوحيد الذي يشغل 'use cache'، و cacheLife، و cacheTag معاً. كما أنه يفعل الـ Partial Prerendering كخيار افتراضي، بحيث يتم تقديم الهيكل الثابت فوراً بينما تتدفق الأجزاء الديناميكية2.

الخطوة 2 — وضع معرف المستأجر في عنوان URL

أفضل طريقة لتمرير هوية المستأجر عبر تطبيق Next.js 16 هي استخدام جزء مسار ديناميكي، لأن بارامترات المسار قابلة للتسلسل، وقابلة للتحليل الثابت، وتتدفق بشكل طبيعي كوسائط. أنشئ شجرة المجلدات:

src/app/
  t/
    [tenant]/
      invoices/
        page.tsx
        actions.ts
src/lib/
  invoices.ts
  tenants.ts

ملف src/lib/tenants.ts يقوم بتحويل الـ slug إلى فئة اشتراك. اعتبر هذا بديلاً لدليل المستأجرين الحقيقي لديك:

// src/lib/tenants.ts
export type Tier = 'free' | 'pro'

export interface Tenant {
  id: string
  tier: Tier
}

const DIRECTORY: Record<string, Tenant> = {
  acme: { id: 'acme', tier: 'pro' },
  globex: { id: 'globex', tier: 'free' },
}

export function resolveTenant(slug: string): Tenant | null {
  return DIRECTORY[slug] ?? null
}

ملف src/lib/invoices.ts هو طبقة البيانات. لاحظ أن كل دالة تأخذ tenantId كوسيط صريح:

// src/lib/invoices.ts
import 'server-only'

export interface Invoice {
  id: string
  tenantId: string
  amountCents: number
  paidAt: string | null
}

// Replace with your real Postgres/Drizzle/Prisma read.
export async function fetchInvoicesFromDB(
  tenantId: string,
): Promise<Invoice[]> {
const seed = tenantId.charCodeAt(0)
return Array.from({ length: 3 }, (_, i) => ({
    id: `${tenantId}-${i}`,
    tenantId,
    amountCents: (seed + i) * 1000,
    paidAt: i === 0 ? new Date().toISOString() : null,
  }))
}

يؤدي استخدام 'server-only' إلى فشل عملية البناء إذا قام أي شخص باستيراد هذا الملف من Client Component، مما يحافظ على طبقة البيانات بعيداً عن المتصفح. وسيط tenantId الصريح هو الضمان المنفصل بأن مفتاح التخزين المؤقت يعكس بالفعل أي مستأجر قام بالطلب.

الخطوة 3 — التخزين المؤقت مع معرف المستأجر كوسيط

الآن الجزء الأهم. تأخذ القراءة المخزنة مؤقتاً tenantId وتستدعي cacheTag مع وسم مخصص للمستأجر، بحيث يتضمن مفتاح التخزين المؤقت المستأجر ويكون نطاق الإبطال لكل مستأجر:

// src/lib/invoices.ts (continued)
import { cacheLife, cacheTag } from 'next/cache'
import type { Tier } from './tenants'

export async function getInvoices(
  tenantId: string,
  tier: Tier,
): Promise<Invoice[]> {
  'use cache'
  cacheTag(`tenant:${tenantId`, `tenant:${tenantId:invoices`)
  cacheLife(tier === 'pro' ? 'minutes' : 'hours')
  return fetchInvoicesFromDB(tenantId)
}

هناك ثلاثة أمور تستحق الانتباه هنا.

الوسائط هي المفتاح. كل من tenantId و tier جزء من مفتاح التخزين المؤقت لأن Next.js يقوم بتسلسل وسائط الدالة في المفتاح جنباً إلى جنب مع معرف البناء ومعرف الدالة الثابت8. مستأجر pro ومستأجر free بنفس المعرف سيستقران في إدخالات منفصلة، وهذا ما تريده لأن لديهما ملفات تعريف تحديث مختلفة.

وسمان، وليس وسماً واحداً. tenant:acme هو النطاق الواسع لإبطال شامل للمستأجر (نزاع فوترة، تغيير فئة الاشتراك). tenant:acme:invoices هو النطاق الجراحي المستخدم بواسطة التعديل (mutation) في الخطوة 5. تكوين الوسوم غير مكلف؛ الوسوم ذات قوة فاعلة موحدة (idempotent) وبحد أقصى 256 حرفاً لكل وسم، و 128 وسماً لكل إدخال9.

يتم استدعاء cacheLife مرة واحدة لكل استدعاء. تتطلب الوثائق استدعاء cacheLife مرة واحدة بالضبط لكل استدعاء دالة مخزنة مؤقتاً؛ حل اسم ملف التعريف باستخدام عامل ثلاثي (ternary) يحافظ على عدد الاستدعاءات عند واحد بغض النظر عن فئة الاشتراك10.

استخدمه في الصفحة:

// src/app/t/[tenant]/invoices/page.tsx
import { notFound } from 'next/navigation'
import { Suspense } from 'React'
import { getInvoices } from '@/lib/invoices'
import { resolveTenant } from '@/lib/tenants'

export default async function InvoicesPage({
  params
}: {
  params: Promise<{ tenant: string }>
}) {
  const { tenant: slug } = await params
  const tenant = resolveTenant(slug)
  if (!tenant) notFound()

  return (
    <main className="p-8">
      <h1 className="text-2xl font-bold">Invoices for {tenant.id}</h1>
      <Suspense fallback={<p>Loading invoices…</p>}>
        <InvoicesTable tenantId={tenant.id} tier={tenant.tier} />
      </Suspense>
    </main>
  )
}

async function InvoicesTable({
  tenantId
  tier
}: {
  tenantId: string
  tier: 'free' | 'pro'
}) {
  const invoices = await getInvoices(tenantId, tier)
  return (
    <ul className="mt-4 space-y-2">
      {invoices.map((inv) => (
        <li key={inv.id} className="rounded border p-3">
          <code>{inv.id}</code> — ${(inv.amountCents / 100).toFixed(2)
          {inv.paidAt ? ' (paid)' : ' (open)'
        </li>
      ))}
    </ul>
  )
}

أصبح params عبارة عن Promise في Next.js 16، لذا يتم انتظاره (awaited) داخل Server Component. تتيح حدود Suspense عرض الترميز المحيط أولاً وتدفقه، بينما يتم تحميل الجدول المخزن مؤقتاً بداخله عند حدوث cold cache.

الخطوة 4 — النمط المضاد الذي يرفض Next.js تجميعه

الغريزة التي تُخطئ في التعامل مع مكونات التخزين المؤقت (Cache Components) متعددة المستأجرين هي قراءة المستأجر من مخزن وقت الطلب داخل الدالة المخزنة مؤقتًا:

// src/lib/wrong-context.ts — لا تقم بنشره
import { cookies from 'next/headers'
import { fetchInvoicesFromDB type Invoice } from './invoices'

export async function getInvoicesBroken(): Promise<Invoice[]> {
  'use cache'
  // خطأ في البناء: لا يمكن لدالة "use cache" قراءة cookies/headers/searchParams
  // لأن مفتاح التخزين المؤقت لا يمكنه عكس القيم التي تختلف باختلاف الطلب.
  const tenantId = (await cookies()).get('tenant')?.value ?? 'unknown'
  return fetchInvoicesFromDB(tenantId)
}

يمنع Next.js استخدام cookies()، و headers()، و searchParams داخل أي نطاق 'use cache' — تحديدًا لأن القيمة الخاصة بكل طلب لا يمكنها قانونيًا المشاركة في مفتاح يتم حسابه لإعادة الاستخدام1. يظهر الخطأ بسرعة وبوضوح، وهو النسخة الودية من وضع الفشل. الشكل الخطير لنفس الخطأ هو دالة تكون قيمة المستأجر الملتقطة في إغلاقها (closure-captured) غير قابلة للتسلسل (مثل مثيل فئة، أو مرجع دالة تم إرجاعه بواسطة getter مستورد، أو كائن وقت التشغيل فقط) — تشير الوثائق إلى أن القيم المغلقة غير القابلة للتسلسل "يمكن تمريرها فقط ولا يمكن فحصها أو تعديلها" داخل دالة مخزنة مؤقتًا1. تعامل مع أي من هذه الأنماط كإعادة هيكلة (refactor)، وليس كحل مؤقت: اقرأ قيمة المستأجر خارج حدود 'use cache' — من params، أو من تخطيط (layout) قام بحلها بالفعل، أو من استدعاء cookies() من جانب الخادم في أعلى التسلسل — ومررها كسلسلة نصية عادية. هذا يضع هوية المستأجر مباشرة في مفتاح التخزين المؤقت، وهو ما تفعله الخطوة 3.

الخطوة 5 — إبطال التخزين المؤقت بنطاق المستأجر باستخدام updateTag

عندما يدفع مستأجر فاتورة، يجب تحديث إدخال هذا المستأجر فقط. تعمل updateTag حصريًا داخل Server Actions وتمنحك دلالات "اقرأ ما كتبته" (read-your-own-writes) — حيث ينتظر الطلب التالي البيانات الجديدة بدلاً من تقديم بيانات قديمة11:

// src/app/t/[tenant]/invoices/actions.ts
'use server'

import { updateTag from 'next/cache'
import { resolveTenant from '@/lib/tenants'

export async function markInvoicePaid(
  slug: string
  invoiceId: string
): Promise<{ ok: true | { ok: false; error: string > {
  const tenant = resolveTenant(slug)
  if (!tenant) return { ok: false error: 'unknown tenant' 
  // استبدل هذا بتحديث UPDATE حقيقي في جدول الفواتير الخاص بك.
  await new Promise((r) => setTimeout(r 50))

  // إبطال ذاكرة التخزين المؤقت لفواتير هذا المستأجر فقط.
  updateTag(`tenant:${tenant.id:invoices`)
  return { ok: true }

قم بربط الإجراء بنموذج صغير في الصفحة:

// src/app/t/[tenant]/invoices/page.tsx (إضافات)
import { markInvoicePaid from './actions'
// ...داخل Server Component، قم برندر هذا بجانب العنوان:
<form
  action={async (formData) => {
    'use server'
    const invoiceId = String(formData.get('invoiceId') ?? '')
    await markInvoicePaid(slug invoiceId)
  }  className="mt-4 flex gap-2"
>
  <input
    name="invoiceId"
    placeholder="معرف الفاتورة"
    className="rounded border px-2 py-1"
  />
  <button type="submit" className="rounded bg-black px-3 py-1 text-white">
    تحديد كمدفوعة
  </button>
</form>

قاعدتان تاليتان. أولاً، تطلق updateTag خطأً إذا استدعيتها خارج Server Action — الوثائق صريحة بشأن هذا وتعتبر Route Handlers والمهام الخلفية سياقات محظورة11. من webhook في Route Handler، استخدم revalidateTag(tag, 'max') بدلاً من ذلك لدلالات stale-while-revalidate3. ثانيًا، في النشر متعدد المثيلات (multi-instance)، تكون كلتا الدالتين محليتين لمثيل واحد افتراضيًا؛ لإبطال التخزين المؤقت على مستوى المجموعة (cluster-wide)، تحتاج إلى معالج تخزين مؤقت مخصص يكتب في مخزن مشترك مثل Redis12.

الخطوة 6 — ملف تعريف cacheLife مخصص لفئة pro

تغطي ملفات التعريف المدمجة الحالات الشائعة: seconds للبيانات المباشرة، و minutes للموجزات، و hours للمخزون، و days لمحتوى المدونة، و weeks، و max للمحتوى المستقر. يوازن كل منها بين ثلاثة توقيتات — stale (موجه العميل)، و revalidate (تحديث الخادم في الخلفية)، و expire (إعادة التوليد المتزامن عند الطلب التالي بعد فترة هدوء)10. بالنسبة لخدمة SaaS حيث تعد فئة pro بفواتير تقترب من الوقت الفعلي ولكنك لا تزال تريد تخزينًا مؤقتًا، قم بتجاوز أو توسيع ذلك بملف تعريف مخصص في next.config.ts:

// next.config.ts
import type { NextConfig from 'next'

const nextConfig: NextConfig = {
  cacheComponents: true
  cacheLife: {
    proInvoices: {
      stale: 30       // تخزين مؤقت للعميل لمدة 30 ثانية؛ يتم فرض حد أدنى 30 ثانية على أي حال
      revalidate: 60  // تحديث في الخلفية كل دقيقة
      expire: 600     // انتهاء صلاحية نهائي بعد 10 دقائق
    }
  }
}

export default nextConfig

أي خاصية تحذفها ترث من ملف التعريف default، بما في ذلك الشكل المضمن cacheLife({})10. أشر إلى ملف التعريف المسمى في طبقة البيانات:

// src/lib/invoices.ts (تحديث)
cacheLife(tier === 'pro' ? 'proInvoices' : 'hours')

يتم فرض الحد الأدنى لعميل يبلغ 30 ثانية بواسطة الموجه (router) حتى تظل الروابط التي تم جلبها مسبقًا (prefetched) قابلة للنقر؛ لا تحاول تقليل القيمة في حقل stale10. قاعدة "الثقب الديناميكي" (dynamic-hole) هي الجانب الآخر الذي يجب مراقبته: أي ملف تعريف يحتوي على revalidate: 0 أو expire أقل من 5 دقائق — بما في ذلك ملف التعريف المدمج seconds — يتم استبعاده من عمليات التوليد المسبق (prerenders) ويصبح "ثقبًا ديناميكيًا" يجب أن يتواجد داخل حدود Suspense10. ملف التعريف proInvoices أعلاه (expire: 600) يتجاوز هذا الحد بشكل مريح، لذا يظل مؤهلاً للتوليد المسبق على المسارات التي تكون ثابتة بخلاف ذلك. في المسار الديناميكي في الخطوة 3، لا تزال حدود Suspense تؤدي دورها عند تحميل التخزين المؤقت البارد، وستصبح مطلوبة هيكليًا إذا قمت بنقل الجدول إلى cacheLife('seconds').

التحقق

قم بتشغيل خادم التطوير مع تفعيل تسجيل التخزين المؤقت:

NEXT_PRIVATE_DEBUG_CACHE=1 pnpm dev

قم بزيارة كل مستأجر وتحقق من أن الاستجابة تحتوي على بيانات المستأجر الصحيحة:

curl -s http://localhost:3000/t/acme/invoices    | grep -c 'acme-0'   # → 1
curl -s http://localhost:3000/t/globex/invoices  | grep -c 'globex-0' # → 1
curl -s http://localhost:3000/t/acme/invoices    | grep -c 'globex-0' # → 0

أول تأكيدين هما اللذان يكشفان تسرب البيانات بين المستأجرين: يجب أن تعرض صفحة كل مستأجر معرفات الفواتير الخاصة به. إذا أرجع الأمر الثاني 0 بدلاً من 1، فإن صفحة globex تقدم بيانات acme المخزنة مؤقتًا — مفتاح التخزين المؤقت لا يرى المستأجر، لذا أعد قراءة الخطوة 4. التأكيد الثالث هو التحقق العكسي: يجب ألا تحتوي صفحة acme أبدًا على فاتورة globex. تُصدر وحدة تحكم التطوير مخرجات الدالة المخزنة مؤقتًا ببادئة Cache وترويسة x-nextjs-stale-time في الاستجابات حتى تتمكن من مراقبة ما يتم تقديمه ومن أين5.

للتحقق في وقت البناء، قم بتشغيل pnpm build وافحص جدول المسارات. يميز Next.js المسارات التي تم توليدها مسبقًا بشكل ثابت بـ والمسارات الديناميكية بـ ƒ13. نظرًا لأن /t/[tenant]/invoices هو مسار بمعامل ديناميكي بدون generateStaticParams، فسيظهر ƒ — وهذا متوقع، ولا تزال مكونات التخزين المؤقت تبث الجدول المخزن مؤقتًا داخل Suspense. الزيارة الثانية لنفس المستأجر تعود من التخزين المؤقت في الذاكرة، وهو ما يتيح لك NEXT_PRIVATE_DEBUG_CACHE=1 مراقبته في وحدة تحكم التطوير.

الأخطاء الشائعة

Error: Route used "cookies" inside "use cache" — لقد حاولت قراءة cookies()، أو headers()، أو searchParams داخل دالة مخزنة مؤقتًا. انقل القراءة إلى ما فوق حدود 'use cache' ومرر القيمة كوسيط1. إذا كنت لا تستطيع حقًا إعادة الهيكلة، فإن توجيه 'use cache: private' موجود لتلك الحالة الاستثنائية ولكنه يستبعد النتيجة من تخزين التوليد المسبق المؤقت.

Error: Filling a cache during prerender timed out — لقد مررت Promise غير محلول يعتمد على بيانات وقت التشغيل إلى دالة مخزنة مؤقتًا. الأسباب الشائعة: تمرير cookies() كخاصية Promise، أو سحب قيمة وقت التشغيل من Map على مستوى الوحدة من داخل 'use cache'. اقرأ قيمة وقت التشغيل في مكون ديناميكي، ثم مرر القيمة المحلولة إلى المكون المخزن مؤقتًا1.

updateTag يلقي خطأ "can only be called from within a Server Action" — لقد قمت باستدعائه من Route Handler أو وظيفة خلفية (background job). من الـ Route Handler، استخدم revalidateTag(tag, 'max') بدلاً من ذلك واقبل دلالات stale-while-revalidate11.

المستأجر أ (Tenant A) لا يزال يرى بيانات المستأجر ب (Tenant B) بعد التعديل — إما أنك استخدمت الوسم 'invoices' بدلاً من tenant:${id}:invoices، أو أن نظامك متعدد النسخ (multi-instance) و updateTag/revalidateTag تم تشغيله على نسخة واحدة فقط. قم بوسم كل إدخال بوسم واحد على الأقل بنطاق المستأجر (tenant-scoped)، وضع معالج تخزين مؤقت مشترك (shared cache handler) أمام المجموعة (cluster)12.

قيمة stale أقل من 30 في cacheLife لا تفعل شيئاً على العميل — يفرض موجه العميل (client router) حداً أدنى قدره 30 ثانية لـ stale حتى لا تنتهي صلاحية الروابط التي تم جلبها مسبقاً (prefetched links) أثناء مرور الماوس فوقها10. تظل قيم revalidate و expire من جانب الخادم كما قمت بتعيينها.

الخطوات التالية

  • استبدل fetchInvoicesFromDB بقراءة حقيقية من Drizzle أو Prisma داخل عملية (transaction). يوضح درس Drizzle ORM + pg-boss نمط العمليات.
  • أضف البث بنطاق المستأجر (tenant-scoped streaming) باستخدام Next.js 16 Suspense + use cache للوحات تحكم أكثر ثراءً.
  • بالنسبة للمجموعات المستضافة ذاتياً (self-hosted cluster)، اكتب cacheHandler مخصصاً يربط updateTag/revalidateTag بـ Redis بحيث تنتقل أحداث إبطال الصلاحية عبر النسخ. يتكامل هذا النمط مع نهج تحديد معدل الطلبات في API باستخدام Upstash Redis sliding window لاعتماد المجموعة على Redis.

Footnotes

  1. Next.js docs, "use cache — Constraints / Request-time APIs" — https://nextjs.org/docs/app/API-reference/directives/use-cache (lastUpdated 2026-05-28). 2 3 4 5 6

  2. Next.js docs, "cacheComponents" — https://nextjs.org/docs/app/API-reference/config/next-config-js/cacheComponents (lastUpdated 2026-05-31). 2

  3. Next.js docs, "revalidateTag function" — https://nextjs.org/docs/app/API-reference/functions/revalidateTag. 2

  4. Next.js docs, "cacheLife (next.config)" — https://nextjs.org/docs/app/API-reference/config/next-config-js/cacheLife.

  5. Next.js docs, "use cache — Debugging cache behavior" — https://nextjs.org/docs/app/API-reference/directives/use-cache (lastUpdated 2026-05-28). 2

  6. endoflife.date, "Node.js" — https://endoflife.date/nodejs (last updated 22 May 2026). 2

  7. npm view next@16.2.6 engines returns { node: '>=20.9.0' } (verified 2026-06-01).

  8. Next.js docs, "use cache — Cache keys" — https://nextjs.org/docs/app/API-reference/directives/use-cache (lastUpdated 2026-05-28). 2

  9. Next.js docs, "cacheTag function" — https://nextjs.org/docs/app/API-reference/functions/cacheTag (lastUpdated 2026-03-20).

  10. Next.js docs, "cacheLife function" — https://nextjs.org/docs/app/API-reference/functions/cacheLife (lastUpdated 2026-05-13). 2 3 4 5 6

  11. توثيق Next.js، "دالة updateTag" — https://nextjs.org/docs/app/API-reference/functions/updateTag (آخر تحديث 28-05-2026). 2 3

  12. توثيق Next.js، "كيفية عمل إعادة التحقق — التنسيق الموزع" — https://nextjs.org/docs/app/guides/how-revalidation-works. 2

  13. توثيق Next.js، "devIndicators" + التحسين الاستاتيكي التلقائي — https://nextjs.org/docs/app/API-reference/config/next-config-js/devIndicators. تشير إلى مسار تم عرضه مسبقاً بشكل استاتيكي؛ ƒ تشير إلى مسار يتم عرضه ديناميكياً.


نشرة أسبوعية مجانية

ابقَ على مسار النيرد

بريد واحد أسبوعياً — دورات، مقالات معمّقة، أدوات، وتجارب ذكاء اصطناعي.

بدون إزعاج. إلغاء الاشتراك في أي وقت.