React: استرجاع البيانات وواجهات برمجة التطبيقات (API) في…
آخر تحديث: 2026-08-26
بعد تحويل مدونته إلى App Router، واجه توم مشكلة جديدة: تباينت وتيرة تحديث البيانات تباينًا كبيرًا بين الصفحات المختلفة — فالمقالات كانت تُحدَّث مرة واحدة شهريًّا، وأسعار المنتجات كانت تُحدَّث عدة مرات يوميًّا، أما الصور الرمزية للمستخدمين فقد كانت تتغير في كل مرة يسجل فيها المستخدم دخوله. وكان عليه اختيار استراتيجيات مختلفة لجلب البيانات بناءً على خصائص كل نوع من أنواع البيانات، مع توفير واجهة API موحدة للواجهة الأمامية في الوقت نفسه. كانت مكونات الخادم (Server Components) في Next.js لاسترداد البيانات ومعالجات المسارات (Route Handlers) هي بالضبط ما كان يحتاجه لحل هذه المشكلات.
1. ما ستتعلمه
- ثلاث استراتيجيات للتخزين المؤقت للبيانات
fetchمباشرةً في مكون الخادم (التخزين المؤقت الإجباري / عدم التخزين / إعادة التحقق من الصحة) - يقوم «Route Handler» بإنشاء نقاط نهاية لواجهة برمجة التطبيقات (API) التي تتبع نمط REST (GET/POST/PUT/DELETE)
- كيفية عمل التوليد الثابت التزايدي (ISR) وكيفية تهيئته
- اعتراض الطلبات وحراس المسار في البرمجيات الوسيطة
- سياسات الوصول الأمنية لمتغيرات البيئة على جانبي الخادم والعميل
- تنفيذ ISR عند الطلب لإعادة التحقق من صحة ذاكرة التخزين المؤقت عند الطلب (revalidateTag / revalidatePath)
- حدود القدرات واعتبارات الأداء الخاصة بالبرمجيات الوسيطة في بيئة التشغيل الطرفية
2. الرسوم التخطيطية المفاهيمية
أوجز توم عملية استرجاع البيانات بالكامل في Next.js: عندما يصل طلب صفحة، يحدد Next.js ما إذا كان سيتخزن البيانات مؤقتًا، أو يعيد التحقق من صحتها، أو يوجهها إلى واجهة برمجة تطبيقات (API) بناءً على استراتيجية استرجاع البيانات. ويساعده فهم هذه العملية على اختيار النهج المناسب لأنواع البيانات المختلفة.
تُعد عقدة القرار {استراتيجية التخزين المؤقت} في مخطط التدفق جوهر هذا القسم — فهي تحدد أداء عملية استرجاع البيانات وقدراتها في الوقت الفعلي. تعد force-cache الأسرع ولكنها لا تعمل في الوقت الفعلي؛ بينما تعد no-store الأسرع في الوقت الفعلي ولكنها ذات الأداء الأسوأ؛ أما revalidate وtags فتقدمان حلولاً مرنة توازن بين هذين الجانبين. تعمل معالجات المسارات (Route Handlers) والبرمجيات الوسيطة (Middleware) كآليات تكميلية، حيث تتولى الأولى إدارة الوصول إلى واجهة برمجة التطبيقات (API) والثانية اعتراض الطلبات.
flowchart TD
A[Page Request] --> B{Server Component}
B --> C[fetch Data]
C --> D{Caching Policy}
D -->|force-cache| E[Read Cache<br/>Default SSG]
D -->|no-store| F[Request every time<br/>Real-time SSR]
D -->|revalidate: N| G[Cache N seconds<br/>ISR Pattern]
D -->|{next: {tags: [...]}}| H[Cache by Tag<br/>On-Demand ISR]
E --> I[Back HTML]
F --> I
G --> I
H --> I
I --> J[Route Handler<br/>/api/*]
J --> K[Database/External API]
I --> L{To be verified?}
L -->|is | M[Middleware<br/>Auth/Redirect]
L -->|No| N[Direct Rendering]
3. سيناريو واقعي
بعد نقل مدونته إلى App Router، واجه توم تحديًا جديدًا يتعلق باسترداد البيانات. ففي السابق، كان يستخدم getStaticProps وgetServerSideProps، اللذين كانا مألوفين له، لكنهما كانا يفتقران إلى المرونة. على سبيل المثال، كان من الضروري تحديث قائمة المقالات مرة كل ساعة، ولكن عند تعديل مقال شائع، كان من الضروري تحديثه على الفور — ولم يكن getStaticProps قادرًا على التعامل مع هذا الأمر على أساس كل صفحة على حدة.
يقدم «App Router» نموذجًا جديدًا تمامًا لاسترداد البيانات: حيث يُستخدم fetch مباشرةً داخل مكونات الخادم، بينما يُستخدم كل من next.revalidate وnext.tags للتحكم الدقيق في استراتيجيات التخزين المؤقت. كما يستخدم توم «معالجات المسارات» (Route Handlers) لإتاحة نقاط نهاية واجهة برمجة التطبيقات (API) لتتمكن مكونات الواجهة الأمامية لقسم التعليقات من استدعائها، ويستخدم «البرمجيات الوسيطة» (Middleware) لحماية مسارات لوحة التحكم. فيما يلي بنية استرجاع البيانات التي صممها للمدونة.
قام أولاً بتحليل خصائص جميع مصادر البيانات على المدونة: محتوى المقالات (يتم تحديثه كل ساعة، ويمكن تخزينه مؤقتًا)، والتعليقات (يتم تحديثها في الوقت الفعلي، ويجب عرضها على الفور)، والإحصائيات (تختلف مع كل زيارة)، ومعلومات المنتجات (يتم تحديثها عدة مرات في اليوم، ويجب عرضها في أسرع وقت ممكن بعد التحديث). ثم اختار استراتيجيات استرجاع مختلفة لكل نوع من أنواع البيانات. ساعده هذا التحليل على تقدير جمال تصميم استرجاع البيانات في Next.js — فهو ليس نهجًا موحدًا يناسب الجميع من نوع «SSG أو SSR»، بل يتيح تحكمًا دقيقًا على أساس كل صفحة، أو كل طلب، أو حتى كل علامة بيانات.
(1) مكون الخادم: استرجاع البيانات
يمكنك استخدام await fetch مباشرةً داخل مكون الخادم — وهذه إحدى أقوى ميزات «App Router». فلا حاجة إلى useEffect، ولا إلى SWR أو React Query، ولا إلى مكتبات إضافية لإدارة الحالة من جانب العميل — ما عليك سوى طلب البيانات مباشرةً داخل دالة المكون، وبمجرد اكتمال العرض من جانب الخادم، يتم إرسالها إلى المتصفح مع كود HTML.
تقبل المعلمة الثانية لـ fetch كائنًا يحتوي على تكوين next، والذي يُستخدم للتحكم في سلوك التخزين المؤقت: { cache: 'force-cache' } تعادل SSG، حيث يتم جلب البيانات مرة واحدة فقط أثناء وقت البناء؛ { cache: 'no-store' } تعادل SSR، حيث يتم استرداد البيانات من جديد مع كل طلب؛ { next: { revalidate: 60 } } تعادل ISR، حيث يتم إعادة التحقق من صحة البيانات بعد 60 ثانية من التخزين المؤقت.
ومن الأمثلة الأكثر تقدمًا على الاستخدام «ISR عند الطلب»: استخدم next: { tags: ['posts'] } لوضع علامة على البيانات، ثم استدعِ revalidateTag('posts') في معالج المسار أو صفحة الإدارة لتشغيل عملية التحديث يدويًّا. وبهذه الطريقة، يمكن إعادة إنشاء المقالات فورًا بعد تحريرها، دون الحاجة إلى انتظار انتهاء فترة إعادة التحقق.
جدول القرار الخاص بثلاث استراتيجيات للتخزين المؤقت
| الاستراتيجية | استرداد التكوين | السلوك | حالات الاستخدام |
|---|---|---|---|
| SSG ثابت | cache: 'force-cache' |
يتم استرداده مرة واحدة أثناء عملية البناء، ثم يتم تقديمه بالكامل عبر شبكة توزيع المحتوى (CDN) | محتوى المقال، صفحة «نبذة عنا» |
| SSR في الوقت الفعلي | cache: 'no-store' |
التحديث عند كل طلب | بيانات المستخدم، لوحة التحكم في الوقت الفعلي |
| ISR التزايدي | next: { revalidate: 60 } |
إعادة التحقق من الصحة بعد 60 ثانية في ذاكرة التخزين المؤقت | قائمة المنتجات، صفحة الأسعار |
| التحديث عند الطلب | next: { tags: ['x'] } |
قم بتخزين revalidateTag() مؤقتًا لتشغيل عملية التحديث |
تحديثات فورية بعد تعديل محتوى نظام إدارة المحتوى (CMS) |
مبادئ توم لاختيار الاستراتيجيات: تستند القرارات إلى وتيرة تغير البيانات ومتطلبات الوقت الفعلي. يستخدم محتوى المقال (الذي يتغير بشكل غير متكرر) استراتيجية SSG؛ ويستخدم عدد التعليقات (الذي يتغير بشكل متكرر ولكنه يمكن أن يتحمل بعض التأخير) استراتيجية ISR، التي يتم تحديثها كل 30 ثانية؛ وتستخدم صور المستخدمين الرمزية (التي تتطلب تحديثات فورية) استراتيجية SSR. ومن خلال الجمع المناسب بين هذه الاستراتيجيات، يمكن إيجاد التوازن الأمثل بين الأداء والاستجابة في الوقت الفعلي.
▶ المثال 1: ثلاث استراتيجيات للتخزين المؤقت لمكونات الخادم
// app/posts/page.tsx - Strategy 1:force-cache(Default SSG)
// Data is retrieved only once during build time.,All subsequent requests go through CDN cache
async function PostsPage() {
const posts = await fetch('https://api.example.com/posts', {
cache: 'force-cache' // equivalent to SSG,Default behavior
}).then(r => r.json())
return (
<div>
<h1>List of Articles</h1>
{posts.map((post: any) => (
<article key={post.id} style={{ marginBottom: 16 }}>
<h2>{post.title}</h2>
<p>{post.body.slice(0, 100)}...</p>
</article>
))}
</div>
)
}
export default PostsPage
// app/dashboard/stats/page.tsx - Strategy 2:no-store(Real-time SSR)
// Every request starts from API Get the latest data
export const dynamic = 'force-dynamic'
async function StatsPage() {
const stats = await fetch('https://api.example.com/dashboard/stats', {
cache: 'no-store' // Request the latest data every time
}).then(r => r.json())
return (
<div>
<h1>Real-Time Statistics</h1>
<p>Users Online:{stats.onlineUsers}</p>
<p>Today PV:{stats.pageViews}</p>
<p>API Number of calls:{stats.apiCalls}</p>
</div>
)
}
export default StatsPage
// app/products/page.tsx - Strategy 3:revalidate(ISR Incremental Update)
// Cache 60s, first visit after 60s triggers a regenerate
async function ProductsPage() {
const products = await fetch('https://api.example.com/products', {
next: { revalidate: 60 } // ISR:60 Please try again in a few seconds.
}).then(r => r.json())
return (
<div>
<h1>Product List</h1>
{products.map((p: any) => (
<div key={p.id} style={{ border: '1px solid #ddd', padding: 12, marginBottom: 8, borderRadius: 8 }}>
<h3>{p.name}</h3>
<p>Price:${p.price}</p>
<p>Inventory:{p.stock > 0 ? 'In stock' : 'Out of stock'}</p>
</div>
))}
</div>
)
}
export default ProductsPage
// app/api/revalidate/route.ts - On-Demand ISR Refresh on Demand
// Call this after the administrator edits the article API Refresh the cache now
import { revalidateTag } from 'next/cache'
export async function POST(request: Request) {
const body = await request.json()
const { tag } = body
// Verification secret Preventing Abuse
const secret = request.headers.get('x-revalidate-secret')
if (secret !== process.env.REVALIDATION_SECRET) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
revalidateTag(tag) // Refresh the cache by tag
return Response.json({ revalidated: true, tag })
}
(2) توجيه واجهة برمجة تطبيقات معالج المسارات
معالج المسار (Route Handler) هو الطريقة المستخدمة لإنشاء نقاط نهاية واجهة برمجة التطبيقات (API) في موجه التطبيقات (App Router). قم بإنشاء ملف باسم route.ts في الدليل app/api/ وقم بتصدير دالة مسماة (GET، POST، PUT، DELETE، PATCH)، حيث تتوافق أسماء الدوال مع طرق HTTP. تدعم معالجات المسارات جميع ميزات مكونات الخادم — فهي يمكنها الوصول إلى قواعد البيانات، واستخدام متغيرات البيئة، والتحكم في سياسات التخزين المؤقت.
استبدل توم البنية الخلفية لـ «Express» التي ذكرها في منشوره السابق على المدونة بـ «Route Handler». وتُتيح عمليات CRUD الخاصة بالمقالات، ومصادقة المستخدمين، ونظام التعليقات، واجهات برمجة تطبيقات (APIs) عبر «Route Handler». وبالاقتران مع «On-Demand ISR»، يتم تحديث ذاكرة التخزين المؤقت فورًا بعد تعديل المقالة، بحيث لا يضطر المستخدمون إلى انتظار انقضاء فترة إعادة التحقق من الصحة.
تدعم معالجات المسارات أيضًا التوجيه الديناميكي. في app/api/products/[id]/route.ts، يتم استرداد معلمات المسار عبر المعلمة params، وهو ما يتناسب مع تصميم واجهات برمجة التطبيقات (API) القائمة على نمط REST. كما يمكن لكل معالج مسار تكوين استراتيجية التخزين المؤقت الخاصة به — على سبيل المثال، يمكن تكوين طلبات GET باستخدام { next: { revalidate: 60 } } لتنفيذ التخزين المؤقت على مستوى واجهة برمجة التطبيقات (API).
دليل مرجعي سريع لاستخدام معالج المسار الأساسي
| مسار الملف | طريقة HTTP | نقطة نهاية URL | الغرض |
|---|---|---|---|
app/api/posts/route.ts |
GET | /api/posts |
الحصول على قائمة المقالات |
app/api/posts/route.ts |
مشاركة | /api/posts |
إنشاء مشاركة جديدة |
app/api/posts/[id]/route.ts |
GET | /api/posts/1 |
استرداد مقال واحد |
app/api/posts/[id]/route.ts |
PUT | /api/posts/1 |
تحديث المقال |
app/api/posts/[id]/route.ts |
حذف | /api/posts/1 |
حذف المشاركة |
app/api/auth/login/route.ts |
نشر | /api/auth/login |
تسجيل دخول المستخدم |
app/api/revalidate/route.ts |
نشر | /api/revalidate |
تحديث ذاكرة التخزين المؤقتة لـ ISR |
▶ المثال 2: واجهة برمجة تطبيقات (API) كاملة لإدارة المدونات (CRUD)
// app/api/posts/route.ts - List of Articles API(GET)and creating articles API(POST)
import { revalidateTag } from 'next/cache'
// Simulated Database
const posts = [
{ id: 1, title: 'Next.js Getting Started', body: 'This article introduces Next.js Basic Usage...', published: true },
{ id: 2, title: 'React 19 New Features', body: 'React 19 What changes has this brought about?...', published: true },
]
export async function GET() {
// Return only published posts
const published = posts.filter(p => p.published)
return Response.json(published)
}
export async function POST(request: Request) {
try {
const body = await request.json()
const newPost = {
id: posts.length + 1,
title: body.title,
body: body.body,
published: body.published ?? false,
createdAt: new Date().toISOString()
}
posts.push(newPost)
// Refresh the article list ISR cache
revalidateTag('posts')
return Response.json(newPost, { status: 201 })
} catch (error) {
return Response.json({ error: 'Invalid request body' }, { status: 400 })
}
}
// app/api/posts/[id]/route.ts - Operations on a Single Article(GET / PUT / DELETE)
export async function GET(
request: Request,
{ params }: { params: { id: string } }
) {
const post = posts.find(p => p.id === Number(params.id))
if (!post) {
return Response.json({ error: 'Post not found' }, { status: 404 })
}
return Response.json(post)
}
export async function PUT(
request: Request,
{ params }: { params: { id: string } }
) {
const body = await request.json()
const index = posts.findIndex(p => p.id === Number(params.id))
if (index === -1) {
return Response.json({ error: 'Post not found' }, { status: 404 })
}
posts[index] = { ...posts[index], ...body, id: Number(params.id) }
revalidateTag('posts')
return Response.json(posts[index])
}
export async function DELETE(
request: Request,
{ params }: { params: { id: string } }
) {
const index = posts.findIndex(p => p.id === Number(params.id))
if (index === -1) {
return Response.json({ error: 'Post not found' }, { status: 404 })
}
posts.splice(index, 1)
revalidateTag('posts')
return Response.json({ message: 'Deleted' })
}
(3) البرامج الوسيطة واعتراض الطلبات
تُعد البرمجيات الوسيطة آلية قوية لاعتراض الطلبات في Next.js. وهي تُنفَّذ قبل وصول كل طلب إلى الصفحة، ويمكن استخدامها للتعامل مع سيناريوهات مثل عمليات إعادة التوجيه، والمصادقة، والتوجيه المُعَلَّم دوليًّا، واختبارات A/B. تعمل البرمجيات الوسيطة في بيئة تشغيل Edge Runtime وتتميز بزمن انتقال منخفض للغاية (في حدود الميلي ثانية).
استخدم توم البرمجيات الوسيطة لتنفيذ ثلاث ميزات: أولاً، إعادة توجيه المستخدمين غير المسجلين إلى صفحة تسجيل الدخول عند دخولهم إلى لوحة التحكم؛ ثانياً، إعادة توجيه المستخدمين تلقائيًّا إلى النسخة اللغوية المناسبة بناءً على إعدادات اللغة في متصفحهم؛ وثالثاً، منع برامج الزحف من الوصول إلى مسارات واجهة برمجة التطبيقات (API).
تستخدم البرمجيات الوسيطة إعدادات matcher لمطابقة المسارات التي يجب اعتراضها، وبذلك تتجنب العبء الزائد غير الضروري على التنفيذ. تجدر الإشارة إلى أن البرمجيات الوسيطة لا يمكنها قراءة req.body أو استخدام واجهات برمجة التطبيقات الأصلية لـ Node.js — فهي تعمل في بيئة Edge.
▶ المثال 3: نظام مصادقة متكامل للبرمجيات الوسيطة
// middleware.ts - Project Root Directory,and app/ Peer
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
// === Features1:Route Guard ===
// Routes Protected from Unauthenticated Users → Redirect to the login page
const token = request.cookies.get('session_token')?.value
const protectedPaths = ['/dashboard', '/admin', '/profile']
if (!token && protectedPaths.some(path => pathname.startsWith(path))) {
const loginUrl = new URL('/login', request.url)
loginUrl.searchParams.set('redirect', pathname)
return NextResponse.redirect(loginUrl)
}
// === Features2:Logged-in users are redirected to the login page → Redirect to the Dashboard ===
if (token && pathname.startsWith('/login')) {
return NextResponse.redirect(new URL('/dashboard', request.url))
}
// === Features3:International Routing ===
// According to Accept-Language Automatically Redirect to Language Version
const supportedLocales = ['zh', 'en', 'ja', 'pt']
const defaultLocale = 'zh'
// Inspection URL Is the language prefix included?
const pathnameHasLocale = supportedLocales.some(
locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
)
if (!pathnameHasLocale) {
const acceptLanguage = request.headers.get('accept-language') || ''
const preferredLocale = supportedLocales.find(locale =>
acceptLanguage.startsWith(locale)
) || defaultLocale
return NextResponse.redirect(new URL(`/${preferredLocale}${pathname}`, request.url))
}
// === Features4:Set Response Headers ===
const response = NextResponse.next()
response.headers.set('X-Frame-Options', 'DENY')
response.headers.set('X-Content-Type-Options', 'nosniff')
return response
}
// Layout middleware Match Path(Required,Otherwise, all routes will be triggered.)
export const config = {
matcher: [
// Match all routes that need to be protected
'/dashboard/:path*',
'/admin/:path*',
'/profile/:path*',
'/login',
// Matching Internationalized Routes
'/((?!api|_next/static|_next/image|favicon.ico).*)',
]
}
دليل تكوين البرامج الوسيطة
| خيار التكوين | الوصف | مثال |
|---|---|---|
matcher |
المسارات التي تتطلب تنفيذ برامج وسيطة | ['/dashboard/:path*', '/login'] |
request.nextUrl |
كائن URL للطلب الحالي | يُستخدم لاسترداد اسم المسار و searchParams |
request.cookies |
قراءة/تشغيل ملفات تعريف الارتباط | request.cookies.get('token') |
NextResponse.redirect() |
إعادة التوجيه إلى عنوان URL محدد | يُستخدم للتحقق من تسجيل الدخول |
NextResponse.next() |
متابعة معالجة الطلب كالمعتاد | السماح بالطلبات الصالحة |
response.headers.set() |
إعداد رؤوس الاستجابة | رؤوس الاستجابة المتعلقة بالأمان |
ترتيب تنفيذ البرامج الوسيطة والاعتبارات ذات الصلة
تُشغَّل البرامج الوسيطة مع كل طلب مطابق، لذا فإن الأداء أمر بالغ الأهمية. وقد صُمم «Edge Runtime» ليعمل في غضون ميكروثانية؛ ولذلك، لا ينبغي إجراء استعلامات قواعد البيانات أو العمليات الحسابية المعقدة داخله. ويتم تحديد ترتيب تنفيذ مكونات البرامج الوسيطة المتعددة وفقًا للترتيب المُعد في matcher. وإذا أعاد أحد مكونات البرامج الوسيطة القيمة redirect() أو rewrite()، فلن يتم تنفيذ مكونات البرامج الوسيطة اللاحقة.
ملاحظة مهمة: هل يلزم عرض متغيرات البيئة في «Middleware» باستخدام البادئة NEXT_PUBLIC_؟ لا — نظرًا لأن «Middleware» يعمل في بيئة خادم، فإنه يمكنه الوصول مباشرةً إلى جميع متغيرات البيئة. ومع ذلك، يجب مراعاة أن البادئة process.env يتم استبدالها بالقيمة الفعلية أثناء ترجمة «Middleware»، لذا لا يمكن قراءة متغيرات البيئة ديناميكيًّا أثناء وقت التشغيل.
4. سياسة أمان متغيرات البيئة
كما واجه توم مشكلة حاسمة عند استخدام معالجات المسارات والبرمجيات الوسيطة: الوصول الآمن إلى متغيرات البيئة. في Next.js، تعتمد قواعد الوصول إلى متغيرات البيئة على البادئة — وهو أمر بالغ الأهمية لاسترجاع البيانات وتطوير واجهات برمجة التطبيقات.
قواعد الوصول إلى متغيرات البيئة
| البادئة | الموقع الذي يمكن الوصول إليه | الوصف |
|---|---|---|
| بدون بادئة | مكون الخادم، معالج المسار، البرمجيات الوسيطة | متاح فقط على جانب الخادم؛ غير معروض للمتصفح |
NEXT_PUBLIC_ |
جميع المواقع (بما في ذلك المتصفح) | سيتم تجميعها وتحويلها إلى لغة JS، لذا يرجى عدم تضمين أي معلومات حساسة |
NEXT_PRIVATE_ |
متاح فقط من جانب الخادم | علامة صريحة جديدة تعمل بنفس طريقة النسخة التي لا تحتوي على بادئة |
قاعدة توم العامة: المعلومات الحساسة مثل سلاسل اتصال قواعد البيانات، والمفاتيح السرية لواجهة برمجة التطبيقات (API)، ومفاتيح JWT — استخدمها دون بادئة، وفقط داخل مكونات الخادم ومعالجات المسارات. أما المتغيرات التي يجب استخدامها في الواجهة الأمامية، مثل معرّفات Google Analytics وعناوين URL العامة لواجهة برمجة التطبيقات (API) — فقم بإضافة البادئة NEXT_PUBLIC_ إليها.
▶ المثال 4: التجديد الثابت التزايدي (ISR) — صفحة منشور المدونة
// app/blog/[slug]/page.tsx - ISR: Regenerate every 60s
interface BlogPost {
title: string
content: string
author: string
publishedAt: string
}
async function getPost(slug: string): Promise<BlogPost> {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: { revalidate: 60 },
})
if (!res.ok) throw new Error('Post not found')
return res.json()
}
export async function generateStaticParams() {
const posts = await fetch('https://api.example.com/posts').then(r => r.json())
return posts.map((post: { slug: string }) => ({ slug: post.slug }))
}
export default async function BlogPostPage({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug)
return (
<article style={{ maxWidth: 700, margin: '0 auto', padding: 24 }}>
<h1>{post.title}</h1>
<p style={{ color: '#999', fontSize: 14 }}>
By {post.author} · {new Date(post.publishedAt).toLocaleDateString()}
</p>
<div style={{ lineHeight: 1.8, marginTop: 16 }}>{post.content}</div>
</article>
)
}
▶ المثال 5: إجراءات الخادم — إرسال النموذج وتغييرات البيانات
// app/actions.ts - Server Actions
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const content = formData.get('content') as string
if (!title || !content) return
await fetch('https://api.example.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title, content, author: 'Alice' }),
})
revalidatePath('/blog')
redirect('/blog')
}
export async function deletePost(slug: string) {
await fetch(`https://api.example.com/posts/${slug}`, { method: 'DELETE' })
revalidatePath('/blog')
}
// app/blog/new/page.tsx - New Article Form
function NewPostPage() {
return (
<div style={{ maxWidth: 600, margin: '0 auto', padding: 24 }}>
<h1>New Post</h1>
<form action={createPost} style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
<input name="title" placeholder="Title" required style={{ padding: 8, borderRadius: 4 }} />
<textarea name="content" placeholder="Content..." rows={8} required style={{ padding: 8, borderRadius: 4 }} />
<button type="submit" style={{ padding: 10, background: '#1890ff', color: 'white', border: 'none', borderRadius: 4, cursor: 'pointer' }}>
Publish
</button>
</form>
</div>
)
}
export default NewPostPage
❓ أسئلة شائعة
fetch في مكون الخادم وuseEffect وfetch على جانب العميل؟fetch في مكون الخادم على الخادم؛ حيث يتم عرض البيانات بتنسيق HTML وإرسالها مباشرةً إلى المتصفح، وبالتالي يرى المستخدم الصفحة كاملةً دون ظهور شاشة تحميل. أما fetch في useEffect فيعمل على جانب العميل؛ حيث يرى المستخدم أولاً صفحة فارغة أو شاشة تحميل، ولا يتم عرض المحتوى إلا بعد انتهاء تنزيل JavaScript وتنفيذه. توفر طريقة fetch في مكونات الخادم تحميلًا أسرع للشاشة الأولى وتكون أكثر ملاءمةً لتحسين محركات البحث (SEO).req.body، أو استخدام وحدات Node.js المدمجة (fs، path)، أو الوصول إلى قواعد البيانات، أو إجراء طلبات خارجية بخلاف fetch. يُنصح بتنفيذ منطق المصادقة المعقد في معالجات التوجيه (Route Handlers).revalidate: 60، ستتم تحديث الصفحة في غضون 60 ثانية كحد أقصى. أما On-Demand ISR فهو تحديث مدفوع بالأحداث — حيث يتم تحديث الصفحة فورًا بعد استدعاء revalidateTag() أو revalidatePath()، مما يجعله مناسبًا للحالات التي تتطلب تفعيل التغييرات في المحتوى على الفور. ويمكن استخدام الاثنين معًا: قم بتعيين وقت إعادة التحقق أطول كخيار احتياطي، مع استخدام On-Demand ISR للتحديث الفوري عند تغيير المحتوى.Access-Control-Allow-Origin مباشرةً في استجابة معالج المسار. يمكنك تضمين دالة مساعدة corsHeaders() التي تُرجع كائن رأس استجابة CORS موحد، وإدراجها في كل معالج مسار باستخدام Response.json(data, { headers: corsHeaders() }). إذا كان الواجهة الأمامية والواجهة الخلفية على نفس النطاق (وهو ما يحدث عادةً في عمليات النشر الكاملة لـ Next.js)، فلا داعي للتعامل مع CORS.📖 ملخص
- يسترد مكون الخادم البيانات مباشرةً من
await fetchويدعم ثلاث استراتيجيات للتخزين المؤقت:force-cache(SSG)، وno-store(SSR)، وnext.revalidate(ISR) - يتم تعريف معالجات المسارات في الملف
route.tsالموجود في الدليلapp/api/؛ وتتوافق أسماء الدوال المصدرة مع طرق HTTP (GET/POST/PUT/DELETE). - يدعم «معالج المسار» التوجيه الديناميكي (
[id])، وتحليل نص الطلب، والتحكم في رموز حالة الاستجابة، وسياسات التخزين المؤقت المخصصة - يقوم ISR بتكوين عملية إعادة التحقق الدورية عبر
next.revalidateوتنفيذ عمليات التحديث عند الطلب عبرnext.tags+revalidateTag() - يتم تنفيذ البرامج الوسيطة قبل وصول الطلب إلى الصفحة؛ حيث تعمل ضمن بيئة تشغيل Edge Runtime، وهي مناسبة لسيناريوهات مثل حراس المسارات، والتدويل، وعمليات إعادة التوجيه، وتعيين رؤوس الأمان.
matcherقم بتكوين نطاق تنفيذ البرامج الوسيطة لتجنب أي عبء غير ضروري على الأداء؛ يمكنك مطابقة مسارات محددة بدقة أو استبعادها.- تعد خدمة ISR عند الطلب مناسبة للحالات التي تتطلب تحديث المحتوى فورًا بعد تحريره؛ ويتم تشغيلها عبر
revalidateTagأوrevalidatePath، دون الحاجة إلى انتظار انتهاء الصلاحية المجدولة. - مبادئ اختيار استراتيجيات استرجاع البيانات: تحديد نهج مختلط يستخدم SSG وISR وSSR بناءً على تواتر تحديث البيانات ومتطلبات التوقيت.
- يجب أن تكون إعدادات
middleware.tsدقيقة لتجنب مطابقة الموارد الثابتة مثل_next/staticوfavicon.ico revalidateTagوrevalidatePathهما واجهتا برمجة التطبيقات (API) الأساسيتان لتنفيذ ISR عند الطلب؛ ويتم استدعاؤهما ضمن معالج المسار أو إجراء الخادم- تُشكل جميع مهارات استرجاع البيانات التي تم تناولها في هذا الدرس الأساس التقني للدروس التالية (نشر CI/CD، ودمج مكتبات المكونات، والمشاريع الشاملة).
📝 تمارين
- قم بإنشاء
app/api/products/route.tsفي المشروع لتنفيذ نموذج لواجهة برمجة تطبيقات (API) للمنتجات — حيث تُرجع عملية GET قائمة بالمنتجات، بينما تُنشئ عملية POST منتجًا جديدًا. فيapp/products/page.tsx، استخدم مكون الخادمfetchلاستدعاء واجهة برمجة التطبيقات هذه وعرض قائمة المنتجات، وقم بتكوينrevalidate: 30لتنفيذ ISR. تحقق من أن الصفحة لا يتم تحديثها عند تعديل بيانات المنتج خلال 30 ثانية؛ وبعد 30 ثانية، قم بتحديث الصفحة لرؤية البيانات الجديدة. - قم بإنشاء
middleware.tsلحماية مسار/dashboard/*— إذا لم تحتوي ملفات تعريف الارتباط علىsession_token، فقم بإعادة التوجيه إلى/login. وفي الوقت نفسه، قم بإعادة توجيه المستخدمين المسجلين الدخول تلقائيًّا إلى/dashboardعند وصولهم إلى/login. استخدمmatcherلتكوين التوجيه بدقة بحيث يتطابق فقط مع المسارات المطلوبة، مما يمنع البرامج الوسيطة من التنفيذ على مسارات الموارد مثل_next/static. - تنفيذ آلية تحديث ISR عند الطلب: انقر على زر «تحديث ذاكرة التخزين المؤقت» في صفحة المسؤول لاستدعاء
POST /api/revalidate(مع مصادقة رأس الطلبx-revalidate-secret) وتحديث ذاكرة التخزين المؤقت لـ ISR لصفحة المنتج عبرrevalidateTag('products'). تحقق مما إذا كانت الصفحة تتحديث فورًا بعد التحديث، وقارن بين سرعات التحديث في كل من ISR المجدول وISR عند الطلب.