0Pricing
Next.js 15 Fullstack (App Router + Server Actions) · درس

إبطال التخزين المؤقت المستند إلى الوسوم للتحديث الدقيق

أضف وسوم التخزين المؤقت إلى عمليات الجلب وأبطل البيانات المتأثرة فقط بعد الطفرات.

إبطال التخزين المؤقت المستند إلى الوسوم للتحديث الدقيق درس مجاني في Next.js 15 Fullstack (App Router + Server Actions) على CoddyKit. هذا هو الدرس 3 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Next.js 15 Fullstack (App Router + Server Actions)، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Next.js 15 Fullstack (App Router + Server Actions) 4 دروس في المجموع.

بعض أجزاء هذا الدرس لم تُترجم بعد وتظهر باللغة الإنجليزية.

Why Tag-Based Invalidation

In Next.js 15, the Data Cache persists fetch results across requests and deployments. The challenge is busting exactly the right entries after a mutation.

  • Time-based revalidation (revalidate: 60) is coarse and clock-driven.
  • Path-based (revalidatePath) clears whole route segments.
  • Tag-based lets you label individual fetches and invalidate just those, no matter which page rendered them.

Tags give you granular, surgical cache busting: update one product, invalidate only the fetches that read that product.

Attaching a Tag to a Fetch

You attach tags through the next.tags option on the extended fetch. Each tag is a plain string you choose.

Here a product list fetch is labelled with two tags: a broad products tag and a specific product-42 tag.

  • The broad tag invalidates the whole collection.
  • The specific tag invalidates one item.
async function getProduct(id: string) {
  const res = await fetch(`https://api.shop.com/products/${id}`, {
    next: { tags: ['products', `product-${id}`] },
  });
  if (!res.ok) throw new Error('Failed to load product');
  return res.json();
}

Invalidating with revalidateTag

After a mutation, call revalidateTag(tag) from next/cache. Every cached fetch carrying that tag is marked stale; the next request re-fetches fresh data.

This runs inside a Server Action or a Route Handler — server-only contexts where mutations happen.

  • revalidateTag does not return a promise you await for re-fetching; it just marks entries stale.
  • Multiple tags? Call it once per tag.
'use server';
import { revalidateTag } from 'next/cache';

export async function updateProduct(id: string, data: FormData) {
  await fetch(`https://api.shop.com/products/${id}`, {
    method: 'PATCH',
    body: JSON.stringify({ name: data.get('name') }),
  });
  // Bust only this product's cached reads
  revalidateTag(`product-${id}`);
}

Broad vs Narrow Tags

Design a small tag hierarchy so you can choose the right blast radius:

  • products — invalidate the whole catalog (e.g. after a bulk import).
  • product-42 — invalidate a single item (e.g. an edit).
  • product-42-reviews — invalidate a related slice without touching the product itself.

Rule of thumb: tag each fetch with both a collection tag and an entity tag. Then mutations can pick the smallest tag that covers the change.

Tagging Reads Across Pages

A tag is global to the Data Cache, not tied to a route. If the home page and the detail page both fetch product-42 with the same tag, one revalidateTag('product-42') busts both.

This is the key advantage over revalidatePath: you don't have to know which pages consumed the data.

// app/page.tsx (home) and app/products/[id]/page.tsx both call this
export async function getProductSummary(id: string) {
  const res = await fetch(`https://api.shop.com/products/${id}`, {
    next: { tags: [`product-${id}`] },
  });
  return res.json();
}

unstable_cache for Non-fetch Data

Not all data comes from fetch — direct DB queries (Prisma, Drizzle) bypass the Data Cache. Wrap them with unstable_cache to gain tag support.

The third argument takes tags and revalidate. The same revalidateTag call busts these entries too.

import { unstable_cache } from 'next/cache';
import { db } from '@/lib/db';

export const getCachedUser = unstable_cache(
  async (id: string) => db.user.findUnique({ where: { id } }),
  ['user-by-id'],
  { tags: ['users'], revalidate: 3600 },
);

Dynamic Tags in unstable_cache

The cache key parts (second arg) must be static enough to identify the function, but you often want a per-entity tag. Build the tag from the argument and pass it in the options.

Return the function so each call gets the right entity tag, enabling revalidateTag('user-7') precision.

import { unstable_cache } from 'next/cache';
import { db } from '@/lib/db';

export function getCachedUser(id: string) {
  return unstable_cache(
    async () => db.user.findUnique({ where: { id } }),
    ['user', id],
    { tags: [`user-${id}`, 'users'] },
  )();
}

Building Tag Names Safely

Inconsistent tag strings silently fail — a mismatched tag busts nothing. Centralize tag construction in pure helper functions so reads and writes always agree.

This is standalone, idiomatic TypeScript: the helpers produce the exact same strings used in both next.tags and revalidateTag.

const tags = {
  products: () => 'products',
  product: (id: string) => `product-${id}`,
  productReviews: (id: string) => `product-${id}-reviews`,
};

console.log(tags.products());
console.log(tags.product('42'));
console.log(tags.productReviews('42'));

Invalidating Multiple Tags Atomically

One mutation often affects several cached slices. After updating a product you may need to bust the item, the collection listing, and the search index.

Call revalidateTag for each. They all take effect together before the action's response returns to the client.

  • Order doesn't matter — all are marked stale.
  • Keep the set minimal to avoid over-fetching elsewhere.
'use server';
import { revalidateTag } from 'next/cache';

export async function publishProduct(id: string) {
  await fetch(`https://api.shop.com/products/${id}/publish`, { method: 'POST' });
  revalidateTag(`product-${id}`);
  revalidateTag('products');
  revalidateTag('search-index');
}

Tag vs Path: Choosing

Both revalidateTag and revalidatePath exist for different jobs:

  • Use tags when the same data appears on many routes, or when you only want to bust data (not the rendered route cache assumptions of a layout).
  • Use path when an entire route's output changed and you don't track tags there.

They compose: a Server Action can call both. Prefer tags for data, paths for whole-page structural changes.

End-to-End in a Server Action

Here is the full loop: a form posts to a Server Action, the action mutates the backend, then invalidates the precise tags. The next render of any page reading those tags gets fresh data automatically.

Note we bust both the entity tag and the collection tag because the edit changes the listing too.

'use server';
import { revalidateTag } from 'next/cache';
import { redirect } from 'next/navigation';

export async function editProduct(id: string, formData: FormData) {
  await fetch(`https://api.shop.com/products/${id}`, {
    method: 'PATCH',
    body: JSON.stringify({ name: formData.get('name') }),
    headers: { 'Content-Type': 'application/json' },
  });
  revalidateTag(`product-${id}`);
  revalidateTag('products');
  redirect(`/products/${id}`);
}

Quick Check

A product detail fetch is tagged ['products', 'product-42'] and is also shown on the home page list (same tag). You edit only product 42's name. What is the most precise way to refresh both views without over-invalidating other products?

Recap

You learned granular, tag-based cache busting in Next.js 15:

  • Attach tags via next: { tags: [...] } on fetch, and via the tags option of unstable_cache for non-fetch data.
  • Use a collection tag plus a per-entity tag so mutations pick the smallest blast radius.
  • Call revalidateTag(tag) inside Server Actions or Route Handlers — once per tag, atomically.
  • Tags are global: one call refreshes the data on every route that read it, unlike revalidatePath.
  • Centralize tag strings in helpers so reads and writes never drift apart.

الأسئلة الشائعة

هل درس «إبطال التخزين المؤقت المستند إلى الوسوم للتحديث الدقيق» مجاني؟

نعم — نص درس «إبطال التخزين المؤقت المستند إلى الوسوم للتحديث الدقيق» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Next.js 15 Fullstack (App Router + Server Actions)، انتقل إلى CoddyKit PRO. تتضمن دورة Next.js 15 Fullstack (App Router + Server Actions) 4 دروس في المجموع.

ماذا ستتعلم في «إبطال التخزين المؤقت المستند إلى الوسوم للتحديث الدقيق»؟

أضف وسوم التخزين المؤقت إلى عمليات الجلب وأبطل البيانات المتأثرة فقط بعد الطفرات. تتمرن على Next.js 15 Fullstack (App Router + Server Actions) مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Next.js 15 Fullstack (App Router + Server Actions)؟

لا تُشترط خبرة سابقة. Next.js 15 Fullstack (App Router + Server Actions) على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 3 من أصل 4.

كم من الوقت يستغرق درس «إبطال التخزين المؤقت المستند إلى الوسوم للتحديث الدقيق»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Next.js 15 Fullstack (App Router + Server Actions) هذا؟

نعم. كل درس في Next.js 15 Fullstack (App Router + Server Actions) يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. ذاكرات التخزين المؤقت الأربع: الطلب والبيانات والمسار الكامل والموجّه
  2. استراتيجيات إعادة التحقق الزمنية وعند الطلب
  3. إبطال التخزين المؤقت المستند إلى الوسوم للتحديث الدقيق
  4. إلغاء التخزين المؤقت: العرض الديناميكي وno-store
← العودة إلى Next.js 15 Fullstack (App Router + Server Actions)