Route Handlers في Next.js 16: دليل عملي لبناء API في App Router (2026)
دليل عملي لبناء واجهات HTTP في Next.js 16 عبر Route Handlers: من إعداد route.ts إلى البث والتخزين المؤقت وCORS، مع أمثلة كود جاهزة وشرح واقعي من الإنتاج.
Route Handlers هي الطريقة الرسمية في Next.js 16 لبناء نقاط نهاية HTTP داخل App Router عبر ملفات route.ts، وتستخدم واجهات Request وResponse القياسية للويب بدلًا من أسلوب req/res القديم. تحل هذه الآلية محل pages/api بشكل كامل داخل مجلد app/، وتدعم جميع أفعال HTTP وقواعد التخزين المؤقت الجديدة وواجهات البث المباشر عبر ReadableStream. بصراحة، قضيت أشهرًا أتتبع أثر الطلبات في React DevTools وأحرق ساعات في ضبط زمن الاستجابة قبل أن تنقشع الأمور، فسألخّص لك هنا كل ما يهم مهندس الواجهة عمليًا (بما في ذلك الأخطاء التي وقعت فيها بنفسي).
يُنشأ Route Handler في Next.js 16 عبر ملف route.ts داخل app/، مع تصدير دوال متزامنة أو غير متزامنة تحمل أسماء أفعال HTTP مثل GET وPOST.
Route Handlers مناسبة للـ webhooks وواجهات JSON العامة وتطبيقات الجوال، بينما Server Actions أنسب لتعديلات الحالة القادمة من نماذج React داخل نفس التطبيق.
لا يوجد تخزين مؤقت افتراضي في Next.js 16؛ يجب تفعيل 'use cache' أو ضبط revalidate صراحةً للحصول على استجابات ثابتة.
Node.js هو وقت التشغيل الافتراضي الآن، ويمكن التبديل إلى Edge بتصدير export const runtime = 'edge' عند الحاجة إلى زمن كمون منخفض قرب المستخدم.
البث عبر ReadableStream يُخفّض Time to First Byte بشكل ملحوظ في استجابات الذكاء الاصطناعي وتصدير الملفات الكبيرة.
ترحيل pages/api/x.ts إلى app/api/x/route.ts يتطلب استبدال res.json() بـ Response.json() والتخلي عن req.body لصالح await request.json().
ما هي Route Handlers في Next.js 16؟
Route Handlers هي معالجات طلبات HTTP على مستوى الخادم تُعرَّف داخل ملف اسمه route.ts (أو route.js) في أي مسار داخل مجلد app/. تحل مكان pages/api بشكل كامل داخل App Router، وتوفر واجهة متوافقة مع معيار الويب مبنية على fetch بدلًا من كائنات req/res ذات نمط Express. في Next.js 16 تحديدًا (النسخة المستقرة الحالية 16.2.x)، أصبحت هذه الآلية أساسية بعد أن انتقلت مسارات الخادم إلى وقت تشغيل Node.js افتراضيًا، وتم تعطيل التخزين المؤقت التلقائي لتقليل حالات التخزين غير المقصودة التي كانت تُربك المطورين في الإصدارات السابقة.
عمليًا، أنت تكتب دالة تعيد كائن Response ويتولى Next.js توصيلها بمسار URL الذي يطابق موقع الملف على القرص. إذا وضعت الملف في app/api/todos/route.ts فسيستجيب لأي طلب على /api/todos. كل دالة مصدَّرة تحمل اسم فعل HTTP (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS) تصبح تلقائيًا معالجًا لذلك الفعل. يمكنك مراجعة التوثيق الرسمي لـ Route Handlers للاطلاع على المرجع الكامل.
// app/api/hello/route.ts
export async function GET() {
return Response.json({ message: 'Hello from Next.js 16' });
}
يستدعي المتصفح /api/hello ويستقبل استجابة JSON مباشرة دون أي إعداد إضافي. لا حاجة إلى bodyParser ولا إلى إعدادات next.config.js؛ كل شيء يعمل من صفر.
Route Handlers مقابل Server Actions: متى تستخدم كلًا منهما؟
السؤال الأكثر شيوعًا في اجتماعات الترحيل التي أشارك بها: هل نبني نقطة نهاية REST أم نستخدم Server Action؟ القاعدة العملية التي أعمل بها: إذا كان الطالب إنسانًا يضغط زرًا داخل تطبيقنا، استخدم Server Action. إذا كان الطالب آلة (webhook، تطبيق جوال أصلي، سكربت خارجي، تكامل طرف ثالث)، استخدم Route Handler. للاطلاع على كيفية بناء Server Actions بشكل آمن راجع دليل Server Actions للنماذج والتحقق والأمان.
الميزة
Route Handlers
Server Actions
الغرض الأساسي
واجهات HTTP عامة
تعديلات داخلية من React
يستهلك من webhook خارجي
نعم
لا
يعمل من تطبيق جوال أصلي
نعم عبر fetch
لا يمكن استدعاؤها مباشرة
سلامة الأنواع من نهاية إلى نهاية
تُفقد عند الحدود إلا مع طبقة مثل tRPC
محفوظة تلقائيًا
تكامل النماذج <form action>
يدوي
مدمج
قابل للاختبار في Postman
نعم
لا مباشرةً
التخزين المؤقت للاستجابة
عبر 'use cache' وrevalidate
غير قابل للتخزين
في تطبيق حقيقي أُشرف عليه، وجدنا أن دمج الاثنين هو الحل الأمثل: Server Actions لكل نماذج CRUD الداخلية، وRoute Handlers للـ webhooks القادمة من Stripe و GitHub وتصدير التقارير التي يستهلكها فريق العمليات عبر cURL. القاعدة الأمنية المهمة: Server Actions هي نقاط نهاية HTTP عامة تحت الغطاء، لذا تحقق دائمًا من الجلسة والصلاحيات داخل الدالة نفسها، لا في المكون المستدعي فقط.
كيف تنشئ نقطة نهاية API في Next.js 16 خطوة بخطوة؟
لنبني مثالًا كاملًا: نقطة نهاية /api/todos تدعم GET لقراءة القائمة وPOST لإضافة عنصر. أفترض هنا وجود مشروع Next.js 16 جديد أُنشئ عبر npx create-next-app@latest مع تفعيل App Router وTypeScript.
لاحظ خمسة أمور مهمة: (1) استخدمت NextRequest بدلًا من Request للحصول على nextUrl وواجهة كوكيز أذكى. (2) أضفت التحقق عبر مكتبة Zod للتحقق من المخطط لأن Route Handlers لا تفلتر البيانات تلقائيًا. (3) أعدت رمز حالة صحيح (201) على الإنشاء. (4) استخدمت crypto.randomUUID() المدمجة في وقت التشغيل الحديث بدلًا من مكتبة خارجية. (5) نظّفت رسائل الخطأ لتكون قابلة للاستهلاك من الواجهة.
لاختبارها محليًا شغّل الخادم عبر pnpm dev ثم:
curl -X POST http://localhost:3000/api/todos \
-H 'content-type: application/json' \
-d '{"title":"ship the article","priority":"high"}'
استخدام NextRequest و NextResponse بفعالية
الفارق بين Request القياسي وNextRequest ليس تجميليًا. يوسّع NextRequest الواجهة الأصلية بأربع إضافات أستعملها يوميًا:
request.nextUrl: نسخة محلَّلة من URL مع searchParams وpathname وlocale جاهزة دون الحاجة إلى بناء new URL() يدويًا.
request.cookies: كائن مع get, set, delete, وgetAll، متوافق مع RFC 6265 ويتعامل مع المشفَّرة تلقائيًا.
request.geo وrequest.ip: عند النشر على Vercel أو Cloudflare، تحصل على بيانات جغرافية تقريبية دون استدعاء API خارجي.
ترويسات مُغلَّفة بواجهة قياسية عبر request.headers.get('authorization').
أما NextResponse فهو موسِّع لـ Response يضيف NextResponse.json() وNextResponse.redirect() وNextResponse.rewrite(). المثال التالي يوضح كيفية قراءة كوكي، والتحقق منه، وإعادة التوجيه أو الاستجابة بـ JSON بناءً على النتيجة:
// app/api/profile/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { verifySessionToken } from '@/lib/auth';
export async function GET(request: NextRequest) {
const token = request.cookies.get('session')?.value;
if (!token) {
return NextResponse.redirect(new URL('/login', request.nextUrl.origin));
}
const user = await verifySessionToken(token);
if (!user) {
return NextResponse.json({ error: 'InvalidSession' }, { status: 401 });
}
const response = NextResponse.json({ user });
response.headers.set('cache-control', 'private, no-store');
return response;
}
القاعدة التي أوصي بها: استخدم NextRequest/NextResponse افتراضيًا حتى لا تكتشف لاحقًا أنك تحتاج nextUrl أو معالجة الكوكيز. الفارق في الحجم لا يُذكر لأنهما مبنيان فوق نفس الواجهة القياسية.
المسارات الديناميكية والمعاملات في Route Handlers
لبناء نقطة نهاية /api/todos/:id، أنشئ مجلدًا اسمه [id] وضع بداخله route.ts. في Next.js 16 أصبح المعامل الثاني params وعدًا (Promise) لدعم البث والتوليد الجزئي المسبق. هذا تغيير كسر التوافق من نسخة 14، لكنه يجعل الأنواع أوضح.
// app/api/todos/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
type RouteContext = { params: Promise<{ id: string }> };
export async function GET(_req: NextRequest, ctx: RouteContext) {
const { id } = await ctx.params;
const todo = await db.todo.findUnique({ where: { id } });
if (!todo) {
return NextResponse.json({ error: 'NotFound' }, { status: 404 });
}
return NextResponse.json(todo);
}
export async function DELETE(_req: NextRequest, ctx: RouteContext) {
const { id } = await ctx.params;
await db.todo.delete({ where: { id } });
return new NextResponse(null, { status: 204 });
}
للمسارات ذات المقاطع المتعددة مثل [...slug] يصبح النوع Promise<{ slug: string[] }>. لا تنسَ التحقق من الطول قبل تفكيك المصفوفة.
كيف تبث استجابة من Route Handler؟
الاستجابات المتدفقة (streaming) هي المكان الذي تتفوق فيه Route Handlers على أي نمط تقليدي. بدل انتظار اكتمال البيانات، ترسل قطعًا فور توفرها فينخفض Time to First Byte من ثوانٍ إلى مئات الميلي ثانية. في تتبع أُجريته الأسبوع الماضي، انخفض TTFB من 1240 مللي ثانية إلى 180 مللي ثانية بعد التحويل إلى بث. للتعمق في Suspense على مستوى الصفحة، راجع دليل التدفق و Suspense و loading.tsx.
// app/api/chat/route.ts
import { NextRequest } from 'next/server';
export async function POST(request: NextRequest) {
const { prompt } = await request.json();
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
const source = await callLLM(prompt); // returns async iterable of tokens
for await (const chunk of source) {
controller.enqueue(encoder.encode(chunk));
}
controller.close();
},
});
return new Response(stream, {
headers: {
'content-type': 'text/plain; charset=utf-8',
'cache-control': 'no-store',
},
});
}
على جانب العميل استخدم ReadableStream نفسه عبر fetch:
const res = await fetch('/api/chat', {
method: 'POST',
body: JSON.stringify({ prompt }),
});
const reader = res.body!.getReader();
const decoder = new TextDecoder();
let done = false;
while (!done) {
const { value, done: d } = await reader.read();
done = d;
if (value) console.log(decoder.decode(value));
}
هذا النمط يفتح أيضًا الباب لتصدير ملفات CSV ضخمة دون تحميلها بالكامل في الذاكرة، ولإرسال أحداث Server-Sent Events لتحديثات في الوقت الحقيقي دون WebSocket كامل.
Edge Runtime مقابل Node.js Runtime: أيهما تختار؟
منذ Next.js 16 أصبح Node.js هو وقت التشغيل الافتراضي لـ Route Handlers، وهذا انعكس على قرارات كثيرة يومية. Edge Runtime لا يزال متاحًا، لكنه بات خيارًا صريحًا لا افتراضًا. للتبديل:
متى تختار Edge؟ حين يكون الحساب خفيفًا وتحتاج قربًا من المستخدم عالميًا: توجيه بناءً على الجغرافيا، توليد صور Open Graph، معالجة إعادة توجيه بسيطة. متى تلتزم بـ Node.js؟ عند الحاجة إلى مكتبات أصلية (مثل bcrypt أو sharp)، اتصال قواعد بيانات تقليدية مثل PostgreSQL عبر pg، أو أي كود يعتمد على fs أو path. Edge Runtime يعمل على V8 isolates دون Node APIs، فمكتبات كاملة ستفشل عند البناء.
القياس الذي أجريته على تطبيق حقيقي من نيويورك إلى مستخدم في سنغافورة أظهر فرق زمن كمون بين Edge وNode.js من 240 مللي ثانية إلى 420 مللي ثانية على استجابة "مرحبًا بالعالم" — لكن لحظة إضافة استعلام قاعدة بيانات مركزية، تلاشت الميزة لأن الرحلة إلى قاعدة البيانات هي عنق الزجاجة.
هل يخزّن Route Handlers الاستجابات مؤقتًا افتراضيًا؟
الإجابة المختصرة: لا، ليس افتراضيًا في Next.js 16. تغيّرت السياسة الافتراضية لجعل النمط الأكثر توقعًا هو ديناميكي بالكامل، مع تفعيل التخزين المؤقت صراحةً. لتحويل استجابة إلى ثابتة، استخدم توجيه 'use cache' الجديد أو ضبط revalidate:
// app/api/products/route.ts
import { unstable_cacheLife as cacheLife } from 'next/cache';
export async function GET() {
'use cache';
cacheLife('hours');
const products = await db.product.findMany();
return Response.json({ products });
}
للتوسع في هذا الموضوع بعمق، راجع دليل التخزين المؤقت في Next.js 16. القاعدة العملية: أي نقطة نهاية GET تعرض بيانات مشتركة بين المستخدمين مرشحة للتخزين، وأي نقطة تعتمد على الجلسة أو الكوكيز يجب أن تظل ديناميكية. تجنب Cache-Control اليدوي إلا إذا كنت تعرف بالضبط ما تفعله؛ Next.js يديره لك بناءً على التوجيهات الجديدة.
لإبطال التخزين من داخل معالج آخر (مثلًا بعد POST) استخدم revalidateTag أو revalidatePath من next/cache. الوسم cacheTag('products') ثم استدعاء revalidateTag('products') بعد الكتابة يحافظ على تناسق البيانات بين المعالجات المختلفة.
كيف تعالج CORS في Route Handlers؟
ليس هناك إعداد سحري لـ CORS في Next.js. أنت تُعيد الترويسات يدويًا لأن Route Handlers استجابات HTTP خام. أضِف معالج OPTIONS لطلبات preflight ومعالج فعلي لكل فعل مسموح:
تجنب * لسياسة allow-origin إلا إذا كانت نقطة النهاية حقًا عامة وبدون كوكيز. للمصادقة عبر الكوكيز يجب أن يكون الأصل محددًا وأن تُضاف access-control-allow-credentials: true. إن كنت تحتاج CORS في مواضع كثيرة، ضع منطق التصفية في proxy.ts بدلًا من نسخ الترويسات، وراجع دليل Middleware و proxy.ts في Next.js 16 لتفاصيل ذلك.
أنماط عملية: Webhooks وتحديد المعدل
Webhooks حالة الاستخدام النموذجية التي تدفع الفرق نحو Route Handlers. المتطلبات الحرجة: تحقق من التوقيع، تجاهل التخزين المؤقت، رد سريع، وتسجيل شامل. مثال لمعالج Stripe:
// app/api/webhooks/stripe/route.ts
import Stripe from 'stripe';
import { NextRequest, NextResponse } from 'next/server';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const secret = process.env.STRIPE_WEBHOOK_SECRET!;
export async function POST(request: NextRequest) {
const signature = request.headers.get('stripe-signature');
if (!signature) return new NextResponse('missing signature', { status: 400 });
const raw = await request.text();
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(raw, signature, secret);
} catch (err) {
return new NextResponse(`invalid signature: ${(err as Error).message}`, { status: 400 });
}
switch (event.type) {
case 'checkout.session.completed':
await fulfillOrder(event.data.object.id);
break;
default:
console.warn('unhandled stripe event', event.type);
}
return NextResponse.json({ received: true });
}
لاحظ استدعاء request.text() بدل request.json(). Stripe يوقّع الجسم الخام بايت-ببايت، ولو حللت JSON أولًا فسيتغير التسلسل ويفشل التحقق.
لتحديد المعدل، أفضل الحلول اليوم استخدام @upstash/ratelimit فوق Redis. مثال مصغّر:
الخطأ الأكثر تكرارًا الذي ألتقطه في المراجعات: نسخ دالة handler وحيدة تعالج if/else لأفعال HTTP بدلًا من تقسيمها إلى دوال مصدَّرة. هذا يعمل، لكنه يخسر ميزة أنّ Next.js يمكنه تحسين كل فعل بشكل مستقل (تخزين مؤقت لـ GET، تخطيه لـ POST). للاطلاع على المرجع الحديث لبنية الملف، راجع مرجع route.js في التوثيق الرسمي.
الأسئلة الشائعة
ما الفرق بين Route Handlers و API Routes في Next.js؟
API Routes هي النموذج القديم في Pages Router المبني على req/res، بينما Route Handlers هي النموذج الحديث في App Router المبني على معايير Request/Response القياسية للويب. Route Handlers توفر دعمًا أفضل للبث والأنواع ولا تتطلب bodyParser يدويًا.
هل تحل Route Handlers محل Server Actions تمامًا؟
لا، هما مكمّلان. استخدم Server Actions للتعديلات القادمة من نماذج React داخل تطبيقك، واستخدم Route Handlers للـ webhooks وواجهات JSON العامة وتطبيقات الجوال. القاعدة العملية: إذا استدعى الآلة نقطة النهاية فاجعلها Route Handler.
كيف أتحكم في وقت التشغيل بين Node.js و Edge؟
صدّر ثابتًا اسمه runtime من ملف route.ts بقيمة 'edge' أو 'nodejs'. Node.js هو الافتراضي في Next.js 16. اختر Edge للحسابات الخفيفة الحساسة لزمن الكمون العالمي، وNode.js عند الحاجة إلى مكتبات أصلية أو اتصال قواعد بيانات تقليدية.
هل يمكن لـ Route Handler إعادة توجيه المستخدم؟
نعم عبر NextResponse.redirect(new URL('/path', request.nextUrl.origin)). أعد رمز الحالة 307 للتوجيه المؤقت أو 308 للدائم. لاحظ أن التوجيه من نداء fetch يعتمد على قيمة redirect في العميل، فاختبر السلوك من كلا الجانبين.
كيف أعالج ملفات مرفوعة عبر Route Handler؟
استخدم await request.formData() ثم formData.get('file') للحصول على كائن File قياسي. اقرأ محتواه بـ await file.arrayBuffer(). لا تحتاج إلى إعدادات خاصة في next.config.js، لكن راقب حجم الجسم واستخدم البث للملفات الكبيرة لتجنب استنزاف الذاكرة.
دليل عملي شامل لـ Turbopack في Next.js 16 من منظور مهندس معماري للواجهة: ترحيل webpack، ضبط ذاكرة build الإنتاج على Vercel، تكوين monorepo مع pnpm، وقياسات أداء ميدانية مقابل Webpack.
كل ما تحتاجه لتفعيل PPR في Next.js 16 خطوة بخطوة: التكوين، حدود Suspense، الفرق عن ISR وSSR، الدمج مع use cache، أخطاء شائعة، ونصائح نشر على Vercel و Node.js و Edge.
دليل عملي وشامل لإعداد المصادقة في Next.js 16 باستخدام Auth.js v5: تسجيل الدخول عبر Google وGitHub وCredentials، إدارة الجلسات، حماية المسارات، وأمثلة كاملة قابلة للتشغيل.