Next.js: مكونات التخزين المؤقت و use cache

آخر تحديث: 2026-08-26

use cache هو أكثر واجهات برمجة التطبيقات تحولاً في Next.js 16 — إنه يرتقي بالتخزين المؤقت من "أثر جانبي لجلب البيانات" إلى "مواطن من الدرجة الأولى على مستوى المكونات".

1. ما ستتعلمه



2. قصة حقيقية لمهندس نظم

(1) نقطة الألم: أربع استدعاءات API متطابقة في نفس الصفحة

أثناء مراجعة لوحة تحكم TaskFlow، لاحظ تشارلي أن المكونات الأربعة في الصفحة — <UserAvatar> و <UserGreeting> و <UserStats> و <UserNotifications> — كل منها كان يستدعي fetch('/api/user'). بينما كان التخزين المؤقت لنفس الرابط يعمل بشكل جيد، تم استدعاء دالة استعلام قاعدة البيانات getUserFromDB() أربع مرات. ينطبق التخزين المؤقت لـ fetch فقط على طلبات HTTP؛ وهو غير فعال تماماً ضد استدعاءات الدوال الداخلية للخادم.

السؤال البيانات
استدعاءات قاعدة البيانات على مستوى الصفحة 4 استعلامات متطابقة
الوقت لكل استعلام 200 مللي ثانية
إجمالي الوقت الإضافي 600 مللي ثانية مهدرة
هدر QPS لقاعدة البيانات 4 أضعاف

(2) الحل باستخدام use cache

قم بتغليف الدالة بـ use cache() — هذا يخزن أي استدعاءات داخلية للدوال مؤقتاً، بما في ذلك استعلامات قاعدة البيانات والعمليات الحسابية وقراءة الملفات.

TSX
// app/dashboard/page.tsx
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache'

async function getUserData() {
  'use cache'
  cacheTag('user-data')
  cacheLife({ stale: 300, revalidate: 600 })

  const user = await db.user.findUnique({ where: { id: 1 } })  // تنفذ مرة واحدة فقط
  return user
}

export default async function DashboardPage() {
  const user = await getUserData()  // أول استدعاء ينفذ استعلام قاعدة البيانات
  // الاستدعاءات اللاحقة ستعيد النتيجة المخزنة مؤقتاً مباشرة

  return (
    <div>
      <UserAvatar user={user} />
      <UserGreeting user={user} />
      <UserStats user={user} />
      <UserNotifications user={user} />
    </div>
  )
}

(3) العائد

البُعد النموذج التقليدي use cache
عدد استعلامات قاعدة البيانات 4 1
التأخير الإضافي 600 مللي ثانية 0 مللي ثانية
دقة التخزين المؤقت مستوى الرابط مستوى الدالة/المكون
تخزين مؤقت للمنطق المخصص ❌ غير مدعوم (fetch لـ HTTP فقط) ✅ يدعم أي كود


3. توجيه use cache() و Content Cache

use cache هو توجيه على مستوى الدالة — أضف 'use cache' في أعلى الدالة للإشارة إلى أن نتيجة الدالة يجب تخزينها مؤقتاً. يُسمى المحتوى المخزن مؤقتاً Content Cache، وهو طبقة تخزين مؤقت جديدة كلياً قُدمت في Next.js 16 ومستقلة عن Data Cache التقليدي (التخزين المؤقت لـ fetch).

100%
graph TB
    subgraph "Next.js 16 Cache System"
        A[Request Memoization<br/>ذاكرة على مستوى الطلب]
        B[Data Cache<br/>تخزين fetch المؤقت]
        C[Content Cache<br/>use cache]
        D[Full Route Cache<br/>تخزين المسار الكامل المؤقت]
    end

    A --> E[داخل نفس الطلب<br/>نفس fetch إزالة التكرار]
    B --> F[استمرار عبر الطلبات<br/>force-cache/no-store]
    C --> G[تخزين نتائج الدوال العشوائية مؤقتاً<br/>تحكم tag + life]
    D --> H[تخزين HTML على مستوى الصفحة مؤقتاً]

    style C fill:#d4edda
مستوى التخزين المؤقت النطاق المُحفز دورة الحياة
Request Memoization طلب واحد تلقائي (نفس الرابط) نهاية الطلب
Data Cache عبر الطلبات fetch(options) يعتمد على الإعداد
Content Cache عبر الطلبات توجيه 'use cache' cacheLife + cacheTag
Full Route Cache عبر الطلبات وقت البناء / وقت التشغيل Revalidate / عند الطلب

(1) الصيغة الأساسية

TSX
// app/cache-demo/actions.ts
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache'

export async function getExpensiveData(id: string) {
  'use cache'
  cacheTag('expensive', `id-${id}`)         // الوسم
  cacheLife({ stale: 60, revalidate: 300 }) // إرجاع خلال 60 ثانية stale، إعادة المحاولة بعد 300 ثانية

  // جميع العمليات داخل هذه الدالة مخزنة مؤقتاً
  const result = await db.query(...)
  return result
}

(2) إعدادات cacheLife

المعامل النوع الوصف مثال
stale number (بالثواني) إرجاع النتيجة مباشرة أثناء صلاحية التخزين المؤقت؛ لا يشغل تحديثاً في الخلفية { stale: 60 }
revalidate number (بالثواني) إعادة تنفيذ الدالة بعد انقضاء هذه المدة { revalidate: 3600 }
expire number (بالثواني) انتهاء صلاحية التخزين المؤقت بشكل مطلق؛ إجبار على إعادة الجلب { expire: 86400 }

▶ مثال: الاستخدام الأساسي لـ use cache (مستوى الصعوبة ⭐)

المخرجات:

TEXT 📖 للعرض فقط
يعرض واجهة مستخدم مكون getExpensiveData.
TSX
// app/cache-basic/page.tsx
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache'

async function getServerTime() {
  'use cache'
  cacheTag('server-time')
  cacheLife({ stale: 10, revalidate: 30 })

  // محاكاة عملية تستغرق وقتاً
  await new Promise(resolve => setTimeout(resolve, 1000))
  return { time: new Date().toISOString(), server: process.env.HOSTNAME ?? 'local' }
}

export default async function CacheBasicPage() {
  const [t1, t2, t3] = await Promise.all([
    getServerTime(),
    getServerTime(),
    getServerTime(),
  ])

  return (
    <div>
      <h1>use cache — أساسي</h1>
      <p>الاستدعاء 1: {t1.time}</p>
      <p>الاستدعاء 2: {t2.time}</p>
      <p>الاستدعاء 3: {t3.time}</p>
      <p><em>جميع الاستدعاءات الثلاثة أعادت نفس النتيجة المخزنة مؤقتاً (بدون حساب مكرر)</em></p>
    </div>
  )
}

المخرجات:

TEXT 📖 للعرض فقط
الاستدعاء 1: 2026-07-06T10:00:00.000Z
الاستدعاء 2: 2026-07-06T10:00:00.000Z ← نفس الطابع الزمني (إصابة في التخزين المؤقت)
الاستدعاء 3: 2026-07-06T10:00:00.000Z ← نفس الطابع الزمني (إصابة في التخزين المؤقت)

المخرجات:

TEXT 📖 للعرض فقط
الاستدعاء 1: 2026-07-06T10:00:00.000Z
الاستدعاء 2: 2026-07-06T10:00:00.000Z  ← تداخل بالميلي ثانية في التخزين المؤقت
الاستدعاء 3: 2026-07-06T10:00:00.000Z


4. الاستخدام العملي لـ cacheTag و cacheLife

cacheTag() لوسم المحتوى المخزن مؤقتاً، و cacheLife() للتحكم في سياسة انتهاء الصلاحية. بالدمج مع revalidateTag()، يمكن تحقيق تحكم دقيق في التخزين المؤقت.

(1) استراتيجية الوسم والتصنيف

100%
graph TB
    A[بيانات التطبيق] --> B[بيانات المستخدم]
    A --> C[بيانات المنتج]
    A --> D[بيانات الطلب]
    B --> E[tag: user-profile]
    B --> F[tag: user-settings]
    C --> G[tag: products]
    C --> H[tag: product-{id}]
    D --> I[tag: orders]
    D --> J[tag: order-{id}]

    style A fill:#cce5ff
السياسة أمثلة الوسوم عمليات الإبطال النطاق
دقيق product-42 revalidateTag('product-42') منتج واحد فقط
متوسط products revalidateTag('products') جميع المنتجات
خشن catalog revalidateTag('catalog') الدليل بالكامل

(2) الوسوم الديناميكية

TSX
// app/products/[id]/page.tsx
import { unstable_cacheTag as cacheTag } from 'next/cache'

export default async function ProductPage({ params }: { params: { id: string } }) {
  const product = await getProduct(params.id)
  return <ProductView product={product} />
}

async function getProduct(id: string) {
  'use cache'
  cacheTag('products', `product-${id}`)  // وسوم ديناميكية بناءً على المعاملات
  return fetch(`https://api.example.com/products/${id}`).then(r => r.json())
}

▶ مثال: استراتيجية التخزين المؤقت المتدرج (مستوى الصعوبة: ⭐⭐)

المخرجات:

TEXT 📖 للعرض فقط
يجلب البيانات ويعرض النتيجة.
TSX
// app/cache-strategy/page.tsx
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache'

async function getUserProfile(userId: number) {
  'use cache'
  cacheTag('users', `user-${userId}`)
  cacheLife({ stale: 120, revalidate: 600 })  // دقيقتان stale، 10 دقائق revalidate
  return fetch(`https://jsonplaceholder.typicode.com/users/${userId}`).then(r => r.json())
}

async function getUserPosts(userId: number) {
  'use cache'
  cacheTag('posts', `user-posts-${userId}`)
  cacheLife({ stale: 60, revalidate: 300 })
  return fetch(`https://jsonplaceholder.typicode.com/users/${userId}/posts`).then(r => r.json())
}

export default async function CacheStrategyPage() {
  const [profile, posts] = await Promise.all([
    getUserProfile(1),
    getUserPosts(1),
  ])

  return (
    <div>
      <h1>{profile.name}</h1>
      <p>البريد الإلكتروني: {profile.email}</p>
      <h2>المقالات ({posts.length})</h2>
      <ul>{posts.map((p: any) => <li key={p.id}>{p.title}</li>)}</ul>
    </div>
  )
}

المخرجات:

TEXT 📖 للعرض فقط
يجلب البيانات من جانب الخادم ويعرض قائمة بالعناصر.

▶ مثال: التصفية بالوسم في Server Action لا تعمل (مستوى الصعوبة: ⭐⭐)

المخرجات:

TEXT 📖 للعرض فقط
يعرض المكون واجهة المستخدم الموصوفة في المتصفح.
TSX
// app/cache-invalidation/page.tsx
import { unstable_cacheTag as cacheTag, revalidateTag } from 'next/cache'

async function getTaskList() {
  'use cache'
  cacheTag('tasks')
  return fetch('https://jsonplaceholder.typicode.com/todos?_limit=5').then(r => r.json())
}

export default async function CacheInvalidationPage() {
  const tasks = await getTaskList()
  return (
    <div>
      <h1>قائمة المهام</h1>
      <ul>{tasks.map((t: any) => <li key={t.id}>{t.title}</li>)}</ul>
      <form action={async () => {
        'use server'
        revalidateTag('tasks')  // انقر الزر لتحديث التخزين المؤقت لقائمة المهام فوراً
      }}>
        <button type="submit">تحديث المهام</button>
      </form>
    </div>
  )
}

المخرجات:

TEXT 📖 للعرض فقط
نموذج يحتوي على حقول إدخال وزر إرسال.
عند الإرسال، يقوم Server Action بمعالجة البيانات وإبطال وسوم التخزين المؤقت ذات الصلة.
النص المرئي: قائمة المهام


5. التخزين المؤقت الضمني لـ fetch مقابل التخزين المؤقت الصريح لـ use cache

البُعد التخزين المؤقت الضمني لـ fetch (Data Cache) التخزين المؤقت الصريح لـ use cache (Content Cache)
طريقة التشغيل fetch(url) تلقائي توجيه 'use cache' داخل جسم الدالة
المحتوى المخزن مؤقتاً استجابة HTTP نتيجة أي دالة
حالات الاستخدام استدعاءات API، طلبات HTTP استعلامات قاعدة البيانات، العمليات الحسابية المعقدة، قراءة الملفات
نظام الوسم next: { tags: [...] } دالة cacheTag()
التحكم بالوقت next: { revalidate: N } دالة cacheLife()
وضع الإبطال revalidateTag() / revalidatePath() revalidateTag() (نفس الوسم)

(1) متى تستخدم التخزين المؤقت لـ fetch، ومتى تستخدم use cache؟

السيناريو طريقة التخزين المؤقت الموصى بها السبب
استدعاء APIs خارجية Data Cache (fetch) دعم أصلي لدلالات HTTP
استدعاء قاعدة البيانات Content Cache (use cache) استدعاء قاعدة البيانات ليس fetch لـ HTTP
عمليات حسابية معقدة (50 مللي ثانية+) Content Cache يمكن تخزين أي دالة مؤقتاً
قراءة الملفات/الإعدادات Content Cache لا يمكن تنفيذ عمليات الملفات عبر fetch
معالجة مختلطة HTTP + DB Content Cache تخزين كتل كاملة من المنطق مؤقتاً

▶ مثال: دمج نوعي التخزين المؤقت (مستوى الصعوبة: ⭐⭐⭐)

المخرجات:

TEXT 📖 للعرض فقط
تعرض الصفحة كما هو موصوف أعلاه، مع تحديث واجهة المستخدم بناءً على السلوك الموصوف.
TSX
// app/cache-combo/page.tsx
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache'

// 1. التخزين المؤقت لـ fetch: استدعاء API خارجي
async function getExternalPosts() {
  return fetch('https://jsonplaceholder.typicode.com/posts', {
    next: { tags: ['external-posts'], revalidate: 300 }
  }).then(r => r.json())
}

// 2. use cache: معالجة البيانات
async function getProcessedPosts() {
  'use cache'
  cacheTag('processed-posts')
  cacheLife({ stale: 60, revalidate: 600 })

  const raw = await getExternalPosts()  // يعتمد على التخزين المؤقت لـ fetch
  return (raw as any[]).map((p: any) => ({
    id: p.id,
    title: p.title.toUpperCase(),
    summary: p.body.slice(0, 100),
  }))
}

export default async function CacheComboPage() {
  const posts = await getProcessedPosts()

  return (
    <div>
      <h1>المقالات المعالجة ({posts.length})</h1>
      <ul>{posts.map((p: any) => (
        <li key={p.id}><strong>{p.title}</strong><p>{p.summary}</p></li>
      ))}</ul>
    </div>
  )
}

المخرجات:

TEXT 📖 للعرض فقط
يجلب البيانات من جانب الخادم ويعرض قائمة بالعناصر.


6. مثال كامل: بنية التخزين المؤقت لنظام إدارة المستخدمين

TSX
// app/cache-system/page.tsx — بنية تخزين مؤقت كاملة
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache'

// ======== طبقة البيانات (تخزين مؤقت صريح use cache) ========
type User = { id: number; name: string; email: string }
type Post = { id: number; title: string; body: string }

async function getUsers(): Promise<User[]> {
  'use cache'
  cacheTag('users')
  cacheLife({ stale: 120, revalidate: 600 })
  return fetch('https://jsonplaceholder.typicode.com/users').then(r => r.json())
}

async function getUserPosts(userId: number): Promise<Post[]> {
  'use cache'
  cacheTag('posts', `user-posts-${userId}`)
  cacheLife({ stale: 60, revalidate: 300 })
  return fetch(`https://jsonplaceholder.typicode.com/users/${userId}/posts`).then(r => r.json())
}

async function getStats(users: User[]) {
  'use cache'
  cacheTag('stats')
  cacheLife({ stale: 300, revalidate: 1800 })
  return {
    totalUsers: users.length,
    avgNameLength: users.reduce((s, u) => s + u.name.length, 0) / users.length,
  }
}

// ======== مكونات الصفحة ========
export default async function CacheSystemPage() {
  const users = await getUsers()
  const stats = await getStats(users)

  return (
    <div style={{ maxWidth: 900, margin: '0 auto', padding: 24 }}>
      <h1>إدارة المستخدمين</h1>
      <StatsCard stats={stats} />
      <div style={{ display: 'grid', gap: 16, marginTop: 24 }}>
        {users.map(user => (
          <UserCard key={user.id} user={user} />
        ))}
      </div>
    </div>
  )
}

// ======== المكون الفرعي (تخزين مؤقت مستقل) ========
async function UserCard({ user }: { user: User }) {
  const posts = await getUserPosts(user.id)
  return (
    <div style={{ border: '1px solid #ddd', borderRadius: 8, padding: 16 }}>
      <h2>{user.name}</h2>
      <p style={{ color: '#666' }}>{user.email}</p>
      <details>
        <summary>المقالات ({posts.length})</summary>
        <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>
      </details>
    </div>
  )
}

function StatsCard({ stats }: { stats: { totalUsers: number; avgNameLength: number } }) {
  return (
    <div style={{ background: '#f0f4ff', borderRadius: 8, padding: 16, display: 'flex', gap: 32 }}>
      <div><strong>إجمالي المستخدمين</strong><p style={{ fontSize: 24 }}>{stats.totalUsers}</p></div>
      <div><strong>متوسط طول الاسم</strong><p style={{ fontSize: 24 }}>{stats.avgNameLength.toFixed(1)}</p></div>
    </div>
  )
}

// ======== المهام الإدارية ========
// app/cache-system/actions.ts
'use server'
import { revalidateTag } from 'next/cache'

export async function refreshUserData() {
  revalidateTag('users')        // تحديث قائمة المستخدمين
}

export async function refreshPosts(userId: number) {
  revalidateTag(`user-posts-${userId}`)  // تحديث مقالات مستخدم محدد
}

export async function refreshAll() {
  revalidateTag('users')
  revalidateTag('posts')
  revalidateTag('stats')
}

// app/cache-system/admin-button.tsx
'use client'
export function AdminControls() {
  return (
    <div style={{ display: 'flex', gap: 8, margin: '16px 0' }}>
      <form action={async () => {
        const { refreshUserData } = await import('./actions')
        await refreshUserData()
      }}>
        <button type="submit">تحديث المستخدمين</button>
      </form>
      <form action={async () => {
        const { refreshAll } = await import('./actions')
        await refreshAll()
      }}>
        <button type="submit">تحديث الكل</button>
      </form>
    </div>
  )
}

▶ مثال: التحقق من إصابة التخزين المؤقت (مستوى الصعوبة: ⭐⭐)

المخرجات:

TEXT 📖 للعرض فقط
نموذج يحتوي على حقول إدخال وزر إرسال.
عند الإرسال، يقوم Server Action بمعالجة البيانات وإبطال وسوم التخزين المؤقت ذات الصلة.
النص المرئي: إدارة المستخدمين
TSX
// app/cache-verify/page.tsx — التحقق مما إذا كانت هناك إصابة في التخزين المؤقت
import { unstable_cacheLife as cacheLife, unstable_cacheTag as cacheTag } from 'next/cache'

let callCount = 0

async function getUniqueId() {
  'use cache'
  cacheTag('unique-id')
  cacheLife({ revalidate: 30 })
  callCount++
  return { id: crypto.randomUUID(), calls: callCount }
}

export default async function CacheVerifyPage() {
  const [a, b, c] = await Promise.all([
    getUniqueId(),
    getUniqueId(),
    getUniqueId(),
  ])

  return (
    <div>
      <h1>التحقق من التخزين المؤقت</h1>
      <p>النتيجة أ: {a.id} (الاستدعاء #{a.calls})</p>
      <p>النتيجة ب: {b.id} (الاستدعاء #{b.calls})</p>
      <p>النتيجة ج: {c.id} (الاستدعاء #{c.calls})</p>
      <p><strong>نفس المعرف + calls = 1 → إصابة في التخزين المؤقت</strong></p>
    </div>
  )
}

المخرجات:

TEXT 📖 للعرض فقط
المحتوى: التحقق من التخزين المؤقت | نفس المعرف + calls = 1 → إصابة في التخزين المؤقت

المخرجات:

TEXT 📖 للعرض فقط
النتيجة أ: 550e8400-e29b-41d4-a716-446655440000 (الاستدعاء #1)
النتيجة ب: 550e8400-e29b-41d4-a716-446655440000 (الاستدعاء #1)  ← إصابة في التخزين المؤقت
النتيجة ج: 550e8400-e29b-41d4-a716-446655440000 (الاستدعاء #1)  ← إصابة في التخزين المؤقت

❓ أسئلة شائعة

س ما الفرق بين use cache و useMemo؟
ج useMemo هو hook من جانب العميل يخزن العمليات الحسابية مؤقتاً داخل المتصفح فقط ويكون نطاقه عملية render واحدة. أما use cache فهو توجيه من جانب الخادم يستمر التخزين المؤقت عبر الطلبات، ويدعم الوسوم وأوقات انتهاء الصلاحية، ويسمح لـ Server Actions بإبطاله عند الطلب. لكل منهما حالات استخدام مختلفة تماماً.
س ما الفرق بين "stale" و "revalidate" في cacheLife؟
ج خلال فترة stale، تُعاد النتيجة المخزنة مؤقتاً مباشرة (بدون تشغيل تحديث في الخلفية). إذا تجاوز الوقت stale ولكنه أقل من revalidate، تستمر إعادة النتيجة المخزنة مؤقتاً، ولكن يتم تشغيل إعادة حساب في الخلفية. إذا تجاوز الوقت revalidate، ينتظر النظام نتيجة جديدة. الإعدادات الموصى بها: اضبط stale على التأخير المقبول للمستخدم، و revalidate على أقصى حداثة للبيانات.
س هل يمكن استخدام نفس الوسم لكل من fetch و use cache؟
ج نعم. revalidateTag('products') سيمسح كلًا من الإدخال الموسوم بـ next: { tags: ['products'] } من التخزين المؤقت لبيانات fetch والإدخال الموسوم بـ cacheTag('products') من Content Cache. هذه هي الآلية الأساسية لتحقيق إبطال موحد للتخزين المؤقت.
س هل يمكن استخدام use cache في مكون عميل (client component)؟
ج لا. توجيه 'use cache' صالح فقط في مكونات الخادم (server components) أو دوال الخادم. في مكونات العميل، يجب استخدام useMemo أو حلول التخزين المؤقت من جانب العميل مثل React Query.
س ماذا تعني بادئة unstable_ في unstable_cacheLife و unstable_cacheTag؟
ج تعني أن API لا يزال قيد التطوير وقد يتغير في الإصدارات المستقبلية. وهي متاحة في Next.js 16.2، ولكن نوصي بمتابعة التحديثات الرسمية. عادةً ما تُزال بادئة unstable_ بعد 1-2 إصدارات رئيسية (متوقع في Next.js 17 أو 18 stable).
س أين تُخزن بيانات Content Cache؟
ج بشكل افتراضي، يُخزن Content Cache في الذاكرة (يستمر عبر الطلبات في بيئة الإنتاج). عند النشر على Vercel، يستخدم تخزين الحافة (edge storage). في البيئات ذاتية الاستضافة، يُخزن في نظام الملفات أو في الذاكرة. حجم التخزين المؤقت محدود بذاكرة الخادم؛ وسيستهلك التخزين المؤقت الكبير موارد الذاكرة.

📖 ملخص


📝 تمارين

  1. مسألة أساسية (⭐): أنشئ app/cache-demo/page.tsx واستخدم دالة 'use cache' لتغليف عملية محاكاة تستغرق وقتاً (await new Promise(resolve => setTimeout(resolve, 2000))). استدعها ثلاث مرات في الصفحة وتحقق من أن الاستدعاء الثاني يبدأ بتأخير صفري (إصابة في التخزين المؤقت).

  2. تمرين متقدم (⭐⭐): أنشئ صفحة تتضمن قائمة مستخدمين وقائمة مقالات المستخدمين. استخدم cacheLife({ revalidate: 300 }) لقائمة المستخدمين و cacheLife({ revalidate: 60 }) لقائمة المقالات. أضف زري Server Action في أسفل الصفحة لتحديث وسم المستخدم ووسم المقالة على التوالي. تحقق من أن الوسوم تعمل بشكل مستقل.

  3. تحدٍّ (⭐⭐⭐): نفذ "لوحة تحليلات البيانات" التي تسترجع البيانات من ثلاثة مصادر مختلفة (HTTP API + قاعدة بيانات محاكاة + دوال حسابية) وتستخدم use cache للتخزين المؤقت المركزي. لكل مصدر بيانات استراتيجية cacheLife مختلفة. أضف زر "تحديث الكل بالإجبار" يستدعي revalidateTag لتحديث جميع لوحات المعلومات في وقت واحد. أنشئ أداة لتتبع معدلات إصابة التخزين المؤقت.

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%