كيفية تنفيذ إعادة التوليد الثابت التدريجي (ISR)
يتيح لك التحديث الثابت التدريجي (ISR) ما يلي:
- تحديث المحتوى الثابت دون إعادة بناء الموقع بالكامل
- تقليل حمل الخادم عن طريق تقديم صفحات ثابتة مسبقة التصيير لمعظم الطلبات
- ضمان إضافة رؤوس
cache-control
المناسبة تلقائيًا للصفحات - التعامل مع أعداد كبيرة من صفحات المحتوى دون أوقات بناء طويلة لـ
next build
إليك مثالًا بسيطًا:
import type { GetStaticPaths, GetStaticProps } from 'next'
interface Post {
id: string
title: string
content: string
}
interface Props {
post: Post
}
export const getStaticPaths: GetStaticPaths = async () => {
const posts = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
const paths = posts.map((post: Post) => ({
params: { id: String(post.id) },
}))
// سنقوم بمسبق تصيير هذه المسارات فقط أثناء وقت البناء.
// { fallback: 'blocking' } سيقوم بتصيير الصفحات
// على الخادم عند الطلب إذا لم يكن المسار موجودًا.
return { paths, fallback: false }
}
export const getStaticProps: GetStaticProps<Props> = async ({
params,
}: {
params: { id: string }
}) => {
const post = await fetch(`https://api.vercel.app/blog/${params.id}`).then(
(res) => res.json()
)
return {
props: { post },
// سيقوم Next.js بإبطال ذاكرة التخزين المؤقت عندما
// يصل طلب، على الأكثر مرة كل 60 ثانية.
revalidate: 60,
}
}
export default function Page({ post }: Props) {
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
export async function getStaticPaths() {
const posts = await fetch('https://api.vercel.app/blog').then((res) =>
res.json()
)
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// سنقوم بمسبق تصيير هذه المسارات فقط أثناء وقت البناء.
// { fallback: false } يعني أن المسارات الأخرى يجب أن تعرض خطأ 404.
return { paths, fallback: false }
}
export async function getStaticProps({ params }) {
const post = await fetch(`https://api.vercel.app/blog/${params.id}`).then(
(res) => res.json()
)
return {
props: { post },
// سيقوم Next.js بإبطال ذاكرة التخزين المؤقت عندما
// يصل طلب، على الأكثر مرة كل 60 ثانية.
revalidate: 60,
}
}
export default function Page({ post }) {
return (
<main>
<h1>{post.title}</h1>
<p>{post.content}</p>
</main>
)
}
إليك كيفية عمل هذا المثال:
- أثناء
next build
، يتم إنشاء جميع منشورات المدونة المعروفة (هناك 25 في هذا المثال) - جميع الطلبات الموجهة إلى هذه الصفحات (مثل
/blog/1
) مخزنة مؤقتًا وفورية - بعد مرور 60 ثانية، سيظل الطلب التالي يعرض الصفحة المخزنة (القديمة)
- يتم إبطال ذاكرة التخزين المؤقت ويبدأ إنشاء نسخة جديدة من الصفحة في الخلفية
- بمجرد الإنشاء بنجاح، سيعرض Next.js الصفحة المحدثة ويخزنها مؤقتًا
- إذا تم طلب
/blog/26
، سيقوم Next.js بإنشاء هذه الصفحة وتخزينها مؤقتًا عند الطلب
مرجع
الدوال
أمثلة
التحقق عند الطلب باستخدام res.revalidate()
للحصول على طريقة أكثر دقة لإعادة التحقق، استخدم res.revalidate
لإنشاء صفحة جديدة عند الطلب من جهاز توجيه API.
على سبيل المثال، يمكن استدعاء جهاز توجيه API هذا على /api/revalidate?secret=<token>
لإعادة تحقق منشور مدونة معين. أنشئ رمزًا سريًا معروفًا فقط لتطبيق Next.js الخاص بك. سيتم استخدام هذا السر لمنع الوصول غير المصرح به إلى جهاز توجيه إعادة التحقق API.
import type { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
// تحقق من السر لتأكيد أن هذا طلب صالح
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: 'رمز غير صالح' })
}
try {
// يجب أن يكون هذا المسار الفعلي وليس مسارًا معاد كتابته
// على سبيل المثال، لـ "/posts/[id]" يجب أن يكون "/posts/1"
await res.revalidate('/posts/1')
return res.json({ revalidated: true })
} catch (err) {
// إذا كان هناك خطأ، سيستمر Next.js في
// عرض آخر صفحة تم إنشاؤها بنجاح
return res.status(500).send('خطأ في إعادة التحقق')
}
}
export default async function handler(req, res) {
// تحقق من السر لتأكيد أن هذا طلب صالح
if (req.query.secret !== process.env.MY_SECRET_TOKEN) {
return res.status(401).json({ message: 'رمز غير صالح' })
}
try {
// يجب أن يكون هذا المسار الفعلي وليس مسارًا معاد كتابته
// على سبيل المثال، لـ "/posts/[id]" يجب أن يكون "/posts/1"
await res.revalidate('/posts/1')
return res.json({ revalidated: true })
} catch (err) {
// إذا كان هناك خطأ، سيستمر Next.js في
// عرض آخر صفحة تم إنشاؤها بنجاح
return res.status(500).send('خطأ في إعادة التحقق')
}
}
إذا كنت تستخدم إعادة التحقق عند الطلب، فلن تحتاج إلى تحديد وقت revalidate
داخل getStaticProps
. سيستخدم Next.js القيمة الافتراضية false
(لا إعادة تحقق) وسيعيد تحقق الصفحة فقط عند الطلب عند استدعاء res.revalidate()
.
التعامل مع الاستثناءات غير الملتقطة
إذا حدث خطأ داخل getStaticProps
عند التعامل مع إعادة التوليد في الخلفية، أو إذا قمت برمي خطأ يدويًا، سيستمر عرض آخر صفحة تم إنشاؤها بنجاح. في الطلب التالي، سيقوم Next.js بإعادة محاولة استدعاء getStaticProps
.
import type { GetStaticProps } from 'next'
interface Post {
id: string
title: string
content: string
}
interface Props {
post: Post
}
export const getStaticProps: GetStaticProps<Props> = async ({
params,
}: {
params: { id: string }
}) => {
// إذا ألقى هذا الطلب خطأ غير ممسوك، فلن يقوم Next.js
// بإبطال الصفحة المعروضة حاليًا وسيتم
// إعادة محاولة getStaticProps في الطلب التالي.
const res = await fetch(`https://api.vercel.app/blog/${params.id}`)
const post: Post = await res.json()
if (!res.ok) {
// إذا كان هناك خطأ في الخادم، قد ترغب في
// إلقاء خطأ بدلاً من الإرجاع حتى لا يتم تحديث ذاكرة التخزين المؤقت
// حتى الطلب الناجح التالي.
throw new Error(`فشل في جلب المنشورات، تم استلام الحالة ${res.status}`)
}
return {
props: { post },
// سيقوم Next.js بإبطال ذاكرة التخزين المؤقت عندما
// يصل طلب، على الأكثر مرة كل 60 ثانية.
revalidate: 60,
}
}
export async function getStaticProps({ params }) {
// إذا ألقى هذا الطلب خطأ غير ممسوك، فلن يقوم Next.js
// بإبطال الصفحة المعروضة حاليًا وسيتم
// إعادة محاولة getStaticProps في الطلب التالي.
const res = await fetch(`https://api.vercel.app/blog/${params.id}`)
const post = await res.json()
if (!res.ok) {
// إذا كان هناك خطأ في الخادم، قد ترغب في
// إلقاء خطأ بدلاً من الإرجاع حتى لا يتم تحديث ذاكرة التخزين المؤقت
// حتى الطلب الناجح التالي.
throw new Error(`فشل في جلب المنشورات، تم استلام الحالة ${res.status}`)
}
return {
props: { post },
// سيقوم Next.js بإبطال ذاكرة التخزين المؤقت عندما
// يصل طلب، على الأكثر مرة كل 60 ثانية.
revalidate: 60,
}
}
تخصيص موقع ذاكرة التخزين المؤقت
يمكنك تكوين موقع ذاكرة التخزين المؤقت لـ Next.js إذا كنت تريد الاحتفاظ بالصفحات والبيانات المخزنة مؤقتًا في تخزين دائم، أو مشاركة ذاكرة التخزين المؤقت عبر عدة حاويات أو نسخ من تطبيق Next.js الخاص بك. تعلم المزيد.
استكشاف الأخطاء وإصلاحها
تصحيح البيانات المخزنة مؤقتًا في التطوير المحلي
إذا كنت تستخدم واجهة برمجة التطبيقات fetch
، يمكنك إضافة سجلات إضافية لفهم أي الطلبات مخزنة مؤقتًا أو غير مخزنة. تعلم المزيد عن خيار logging
.
module.exports = {
logging: {
fetches: {
fullUrl: true,
},
},
}
التحقق من سلوك الإنتاج الصحيح
للتحقق من أن صفحاتك مخزنة مؤقتًا ويتم إعادة التحقق منها بشكل صحيح في بيئة الإنتاج، يمكنك الاختبار محليًا عن طريق تشغيل next build
ثم next start
لتشغيل خادم Next.js للإنتاج.
هذا سيسمح لك باختبار سلوك التوليد التدريجي الثابت (ISR) كما يعمل في بيئة الإنتاج. لمزيد من التصحيح، أضف متغير البيئة التالي إلى ملف .env
الخاص بك:
NEXT_PRIVATE_DEBUG_CACHE=1
هذا سيجعل خادم Next.js يسجل في الكونسول عمليات الوصول والإخفاق في ذاكرة التخزين المؤقت لـ ISR. يمكنك فحص المخرجات لمعرفة الصفحات التي يتم توليدها أثناء next build
، وكذلك كيف يتم تحديث الصفحات عند الوصول إلى المسارات عند الطلب.
محاذير
- التوليد التدريجي الثابت (ISR) مدعوم فقط عند استخدام بيئة تشغيل Node.js (الافتراضية).
- التوليد التدريجي الثابت (ISR) غير مدعوم عند إنشاء تصدير ثابت.
- لن يتم تنفيذ Middleware لطلبات ISR عند الطلب، مما يعني أن أي إعادة كتابة للمسار أو منطق في Middleware لن يتم تطبيقه. تأكد من أنك تقوم بإعادة التحقق من المسار الدقيق. على سبيل المثال،
/post/1
بدلاً من/post-1
المعاد كتابتها.
دعم المنصات
خيار النشر | مدعوم |
---|---|
خادم Node.js | نعم |
حاوية Docker | نعم |
تصدير ثابت | لا |
المحولات | حسب المنصة |
تعلم كيفية تكوين ISR عند استضافة Next.js ذاتيًا.
سجل الإصدارات
الإصدار | التغييرات |
---|---|
v14.1.0 | cacheHandler المخصص أصبح مستقرًا. |
v13.0.0 | تم تقديم App Router. |
v12.2.0 | Pages Router: أصبح التوليد التدريجي الثابت عند الطلب (On-Demand ISR) مستقرًا |
v12.0.0 | Pages Router: تمت إضافة الاسترجاع التلقائي لـ ISR مع مراعاة الروبوتات. |
v9.5.0 | Pages Router: تم تقديم ISR المستقر. |