در معماری فروشگاه هدلس، ISR (Incremental Static Regeneration) لایه‌ای است که سرعت SSG (Static Site Generation) و تازگی SSR (Server-Side Rendering) را در یک مدل یکپارچه ترکیب می‌کند و پایه تجربه خرید سریع و به‌روز محسوب می‌شود. بدون استراتژی revalidate، بدون On-Demand Revalidation و بدون پایش دقیق، فروشگاه هدلس به یک سیستم راکد تبدیل می‌شود که محصولات جدیدش دیده نمی‌شوند یا برعکس، هزینه سرور هر دقیقه به‌خاطر SSR افزایش می‌یابد. تفاوت ISR و SSG، انتخاب بازه revalidate و استراتژی Tag-Based Revalidation، تصمیم‌های معماری را به تصمیم‌های تجربه خرید گره می‌زند. On-Demand، Time-Based، Tag-Based و Edge-Based، چهار استراتژی اصلی ISR در فروشگاه هدلس هستند. در این راهنما از ساختار پایه تا استقرار تولیدی ISR در فروشگاه هدلس را با نگاه مهندسی و کد عملی پوشش می‌دهیم.

در پروژه‌های فروشگاهی هدلس، هر بار که تیم بین SSG و SSR تصمیم می‌گیرد، در نهایت به ISR می‌رسد چون نه می‌تواند تازگی محصولات را قربانی کند و نه هزینه سرور SSR را برای همه صفحات بپذیرد. این راهنما از همان نقطه‌ای شروع می‌کند که تجربه می‌گوید بیشترین ارزش را دارد.

ISR چیست و چه تفاوتی با SSG و SSR دارد؟

ISR یک مدل رندر است که در آن صفحه در زمان Build به‌صورت استاتیک ساخته می‌شود، اما در بازه‌های مشخص یا بر اساس درخواست، به‌روزرسانی می‌شود. این ترکیب، سرعت SSG و تازگی SSR را در یک مدل واحد فراهم می‌کند.

معیارSSGSSRISR
زمان Buildکند برای سایت‌های بزرگفوریمتوسط
سرعت TTFBبسیار پایینمتوسطپایین
تازگی محتواکندبالامتوسط تا بالا
هزینه سرورپایینبالاپایین
مناسب فروشگاهمحدودبرای صفحات پویاجامع

راهنمای Headless WordPress چیست و چرا آینده سایت‌های حرفه‌ای است و Headless Commerce یا Traditional Commerce نقطه شروع مناسبی هستند.

چرا ISR برای فروشگاه هدلس انتخاب اول است

صفحه محصول، دسته‌بندی و صفحه اصلی فروشگاه، محتوایی نیمه‌پویا هستند. تغییر می‌کنند اما نه هر ثانیه. ISR با بازه‌های کوتاه revalidate، این تعادل را ممکن می‌کند.

چرخه اجرای ISR در Next.js

Next.js App Router از سه مدل Data Fetching پشتیبانی می‌کند که ISR یکی از آن‌هاست. در ISR، صفحه در Build تولید می‌شود و در اولین درخواست پس از انقضای بازه، در پس‌زمینه به‌روزرسانی می‌شود.

// app/product/[slug]/page.tsx
export const revalidate = 60;

async function getProduct( slug: string ) {
  const res = await fetch(
    `${process.env.WP_API}/wp-json/wp/v2/product?slug=${slug}`,
    { next: { revalidate: 60 } }
  );
  return res.json();
}

export default async function ProductPage( { params } ) {
  const product = await getProduct( params.slug );
  return (
    <article>
      <h1>{product[0].title.rendered}</h1>
      <div dangerouslySetInnerHTML={{ __html: product[0].content.rendered }} />
    </article>
  );
}

Stale-While-Revalidate در عمل

در ISR، کاربر نسخه استاتیک قدیمی را می‌بیند (Stale) و در پس‌زمینه، نسخه جدید تولید می‌شود (Revalidate). این رفتار، TTFB پایین را حتی در زمان به‌روزرسانی تضمین می‌کند.

سه حالت کش در Next.js ISR

1. HIT: نسخه استاتیک معتبر است
2. MISS: نسخه استاتیک وجود ندارد (اولین درخواست)
3. STALE: نسخه استاتیک منقضی شده، اما کاربر نسخه قدیمی را می‌بیند

Time-Based Revalidation در فروشگاه هدلس

Time-Based Revalidation ساده‌ترین و پرکاربردترین مدل ISR است. در این مدل، بازه زمانی مشخص می‌کنید که پس از آن، صفحه در اولین درخواست بعدی به‌روزرسانی شود.

نوع صفحهبازه پیشنهادی
صفحه اصلی60s
صفحه محصول300s
صفحه دسته‌بندی120s
صفحه بلاگ600s
صفحه درباره3600s

پیکربندی Revalidate در Route Segment

// app/product/[slug]/layout.tsx
export const revalidate = 300;

// یا در سطح صفحه
// app/product/[slug]/page.tsx
export const revalidate = 300;

Revalidate در Fetch

const res = await fetch( url, {
  next: { revalidate: 300 },
} );

نکات مهم در انتخاب بازه

- بازه کوتاه‌تر: تازگی بیشتر، بار سرور بیشتر
- بازه بلندتر: بار کمتر، تازگی کمتر
- بازه صفر: معادل SSR
- بازه false: معادل SSG بدون به‌روزرسانی

On-Demand Revalidation با Webhook

On-Demand Revalidation مدل پیشرفته‌تری است که در آن، به‌جای بازه زمانی، بر اساس رویداد، صفحه به‌روزرسانی می‌شود.

// app/api/revalidate/route.ts
import { revalidatePath } from "next/cache";
import { NextRequest, NextResponse } from "next/server";

export async function POST( request: NextRequest ) {
  const secret = request.nextUrl.searchParams.get( "secret" );
  if ( secret !== process.env.REVALIDATE_SECRET ) {
    return NextResponse.json( { error: "Invalid secret" }, { status: 401 } );
  }

  const body = await request.json();
  const path = body.path;

  if ( ! path ) {
    return NextResponse.json( { error: "Missing path" }, { status: 400 } );
  }

  try {
    revalidatePath( path );
    return NextResponse.json( { revalidated: true, path } );
  } catch ( err ) {
    return NextResponse.json( { error: String( err ) }, { status: 500 } );
  }
}

ارسال Webhook از WordPress

<?php
add_action( "save_post_product", function( $post_id ) {
    if ( wp_is_post_revision( $post_id ) ) {
        return;
    }
    $permalink = get_permalink( $post_id );
    if ( ! $permalink ) {
        return;
    }
    $path = wp_parse_url( $permalink, PHP_URL_PATH );
    wp_remote_post( get_option( "wpk_revalidate_endpoint" ) . "?secret=" . get_option( "wpk_revalidate_secret" ), array(
        "headers" => array( "Content-Type" => "application/json" ),
        "body"    => wp_json_encode( array( "path" => $path ) ),
        "timeout" => 10,
    ) );
}, 10, 1 );

راهنمای ساخت Custom Post Type حرفه‌ای و Webhook و اتصال سرویس‌ها نقطه شروع مناسبی هستند.

On-Demand vs Time-Based

Time-Based:
- مزیت: ساده، پیش‌بینی‌پذیر
- عیب: تأخیر تا بازه، بار اضافی

On-Demand:
- مزیت: به‌روزرسانی دقیق بر رویداد
- عیب: نیازمند Webhook، ریسک از دست رفتن رویداد

پیشنهاد ترکیبی: Time-Based به‌عنوان Safety Net، On-Demand برای رویدادهای مهم

Tag-Based Revalidation برای محصولات و دسته‌ها

Tag-Based Revalidation امکان به‌روزرسانی گروهی صفحات مرتبط را فراهم می‌کند. این مدل در فروشگاه‌های بزرگ با هزاران محصول ضروری است.

// در Fetch
const res = await fetch( url, {
  next: {
    revalidate: 300,
    tags: [ `product-${id}`, `category-${categoryId}` ],
  },
} );

On-Demand Revalidation با Tag

// app/api/revalidate/route.ts
import { revalidateTag } from "next/cache";

export async function POST( request: NextRequest ) {
  const secret = request.nextUrl.searchParams.get( "secret" );
  if ( secret !== process.env.REVALIDATE_SECRET ) {
    return NextResponse.json( { error: "Invalid secret" }, { status: 401 } );
  }

  const body = await request.json();
  const tags = body.tags || [];

  for ( const tag of tags ) {
    revalidateTag( tag );
  }
  return NextResponse.json( { revalidated: true, tags } );
}

Tag در WordPress

<?php
add_action( "save_post_product", function( $post_id ) {
    $tags = array(
        "product-{$post_id}",
    );
    $categories = wp_get_post_terms( $post_id, "product_cat", array( "fields" => "ids" ) );
    foreach ( $categories as $cat_id ) {
        $tags[] = "category-{$cat_id}";
    }
    wp_remote_post( get_option( "wpk_revalidate_endpoint" ) . "?secret=" . get_option( "wpk_revalidate_secret" ), array(
        "headers" => array( "Content-Type" => "application/json" ),
        "body"    => wp_json_encode( array( "tags" => $tags ) ),
        "timeout" => 10,
    ) );
}, 10, 1 );

الگوهای Tag در فروشگاه

- product-{id}: به‌روزرسانی صفحه محصول
- category-{id}: به‌روزرسانی صفحه دسته
- brand-{id}: به‌روزرسانی صفحه برند
- home: به‌روزرسانی صفحه اصلی
- global-config: به‌روزرسانی تنظیمات سراسری

اتصال ISR به WordPress به‌عنوان Headless CMS

WordPress به‌عنوان Headless CMS از طریق REST API یا GraphQL به Next.js متصل می‌شود. ISR در این معماری، نقش لایه کش هوشمند را ایفا می‌کند.

الگوی Fetch با WordPress REST API

async function getProducts( categoryId ) {
  const res = await fetch(
    `${process.env.WP_API}/wp-json/wp/v2/product?product_cat=${categoryId}&per_page=24`,
    {
      next: {
        revalidate: 300,
        tags: [ `category-${categoryId}` ],
      },
    }
  );
  if ( ! res.ok ) throw new Error( "Failed to fetch products" );
  return res.json();
}

الگوی Fetch با WPGraphQL

async function getProduct( slug ) {
  const res = await fetch( process.env.WP_GRAPHQL, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify( {
      query: `
        query GetProduct( $slug: ID! ) {
          product( id: $slug, idType: SLUG ) {
            title
            content
            productCategories { nodes { name slug } }
          }
        }
      `,
      variables: { slug },
    } ),
    next: { revalidate: 300, tags: [ `product-${slug}` ] },
  } );
  const json = await res.json();
  return json.data.product;
}

راهنمای WPGraphQL چیست و چرا GraphQL در وردپرس انقلاب کرد و ساخت API اختصاصی در وردپرس نقطه شروع مناسبی هستند.

الگوی Hybrid: REST برای داده، GraphQL برای روابط

در فروشگاه‌های پیچیده، ترکیب REST برای داده ساده و GraphQL برای روابط پیچیده، عملکرد بهتری می‌دهد. ISR در هر دو حالت یکسان عمل می‌کند.

fallback و رفتار صفحات جدید

در Pages Router، پارامتر fallback تعیین می‌کند چه زمانی صفحه‌ای که در Build ساخته نشده، درخواست شود. سه مقدار ممکن:

fallback: false  // صفحه 404
fallback: true   // صفحه Skeleton، سپس رندر
fallback: "blocking" // کاربر منتظر می‌ماند تا رندر شود

مقایسه رفتار fallback

مقدارتجربه کاربرمناسب برای
false404 برای صفحات جدیدسایت بسته
trueSkeleton سریعفروشگاه بزرگ
blockingانتظار، سپس صفحه کاملصفحات مهم

در App Router

در App Router، dynamicParams جایگزین fallback شده است:

// app/product/[slug]/page.tsx
export const dynamicParams = true;  // صفحات جدید تولید می‌شوند
// export const dynamicParams = false; // 404

ISR در Edge Runtime و CDN

ISR روی Vercel به‌صورت پیش‌فرض از Edge Network استفاده می‌کند. این ترکیب، سرعت جهانی را تضمین می‌کند.

// vercel.json
{
  "headers": [
    {
      "source": "/(.*)",
      "headers": [
        { "key": "Cache-Control", "value": "public, max-age=0, must-revalidate" }
      ]
    }
  ]
}

Cache-Control در ISR

s-maxage={revalidate}
stale-while-revalidate={2 * revalidate}

الگوی Multi-Region ISR

- Build در یک Region
- Deploy به Edge در چندین Region
- Revalidate در Region مرکزی
- Propagate به Edge در چند ثانیه

محدودیت‌های Edge Runtime

- دسترسی محدود به Node.js API
- نبود file system
- محدودیت در برخی کتابخانه‌ها
- استفاده از Edge-compatible APIs

پایش و استراتژی Alerting

ISR بدون پایش، می‌تواند صفحات راکد یا Over-Revalidate ایجاد کند.

متریک‌های کلیدی ISR

- Revalidation Rate: نرخ به‌روزرسانی
- Cache HIT Ratio: درصد HIT
- Stale Serving Count: تعداد سرو نسخه Stale
- Build Time: زمان Build
- Revalidate Latency: تأخیر به‌روزرسانی

Alerting هوشمند

<?php
function wpk_isr_check_alerts() {
    // پایش صفحات راکد (بیش از 2 برابر بازه revalidate)
    $stale_pages = wpk_get_stale_pages();
    if ( count( $stale_pages ) > 50 ) {
        wp_mail(
            get_option( "admin_email" ),
            "هشدار ISR: صفحات راکد",
            sprintf( "%d صفحه بیش از حد معمول راکد مانده‌اند.", count( $stale_pages ) )
        );
    }

    // پایش Over-Revalidation
    $revalidate_rate = wpk_get_revalidate_rate();
    if ( $revalidate_rate > 1000 ) {
        wp_mail(
            get_option( "admin_email" ),
            "هشدار ISR: نرخ به‌روزرسانی بالا",
            sprintf( "%d revalidate در ساعت گذشته.", $revalidate_rate )
        );
    }
}

پایش با Vercel Analytics

در Vercel Dashboard:
- ISR Tab: تعداد HIT/MISS/STALE
- Revalidate History: تاریخچه به‌روزرسانی‌ها
- Edge Cache: توزیع جغرافیایی

تست و اعتبارسنجی ISR

تست ISR در سه سطح انجام می‌شود: سطح Build، سطح Revalidation و سطح تجربه کاربر.

تست Build

# Build محلی
npm run build

# بررسی خروجی
.next/server/app/  # صفحات استاتیک
.next/server/pages/  # در Pages Router

# بررسی revalidate در manifest
cat .next/prerender-manifest.json

تست Revalidation

# تست On-Demand
curl -X POST "https://example.com/api/revalidate?secret=XXX" 
  -H "Content-Type: application/json" 
  -d '{"path":"/product/test-product"}'

# بررسی پاسخ
{ "revalidated": true, "path": "/product/test-product" }

تست Cache HIT

# درخواست اول (MISS)
curl -I https://example.com/product/test-product
# x-vercel-cache: MISS

# درخواست دوم (HIT)
curl -I https://example.com/product/test-product
# x-vercel-cache: HIT

# پس از revalidate (STALE)
curl -I https://example.com/product/test-product
# x-vercel-cache: STALE

اشتباهات رایج در تست

اشتباه اول، نبود تست On-Demand. اشتباه دوم، نبود تست Cache HIT. اشتباه سوم، نبود تست با حجم بالا. اشتباه چهارم، نبود تست در Edge Regionهای مختلف. اشتباه پنجم، نبود تست پس از Deploy.

پرسش‌های پرتکرار درباره ISR

تفاوت ISR و SSR چیست؟

در ISR، صفحه در Build ساخته می‌شود و در بازه‌های مشخص به‌روزرسانی می‌شود. در SSR، صفحه در هر درخواست رندر می‌شود.

بهترین بازه Revalidate چقدر است؟

بستگی به نوع صفحه دارد. برای صفحه محصول، ۳۰۰s معمول است.

چگونه On-Demand Revalidation را امن کنم؟

با Secret Token در URL و بررسی آن در Endpoint.

آیا ISR در App Router پشتیبانی می‌شود؟

بله، با export const revalidate و fetch با next.revalidate.

آیا ISR روی Cloudflare Pages کار می‌کند؟

خیر، Cloudflare Pages از ISR پشتیبانی نمی‌کند. Vercel و Netlify پشتیبانی می‌کنند.

چگونه از صفحات راکد جلوگیری کنم؟

با ترکیب Time-Based و On-Demand و پایش Stale Pages.

آیا ISR با WordPress Headless ترکیب می‌شود؟

بله، از طریق REST یا GraphQL و Webhook برای On-Demand. راهنمای Headless WordPress با Next.js App Router را ببینید.

نتیجه و مسیر ادامه

ISR در فروشگاه هدلس، ترکیب سرعت و تازگی را ممکن می‌کند. کلید موفقیت، انتخاب استراتژی Revalidation، ترکیب Time-Based و On-Demand، پایش دقیق و تست در سه سطح است. اگر این لایه‌ها با دقت طراحی شوند، تجربه خرید سریع و تازه به‌صورت همزمان حاصل می‌شود.

پیشنهاد می‌کنم مسیر یادگیری را با Service Worker پیشرفته ادامه دهید و سپس Progressive Web App برای فروشگاه را به‌عنوان رویکرد مکمل مطالعه کنید. همچنین مفهوم Incremental static regeneration را در ویکی‌پدیا مرور کنید.

اگر روی پروژه واقعی خود ISR پیاده کرده‌اید، برایم جالب است بدانید کدام استراتژی — Time-Based یا On-Demand — بیشترین تأثیر را داشته است. تجربه خودتان را در دیدگاه‌ها بنویسید.