مقدمة إلى البرمجيات الوسيطة في Next.js
تتيح لك البرمجيات الوسيطة في Next.js تشغيل كود قبل اكتمال الطلب. يمكنك تعديل الاستجابة عن طريق إعادة الكتابة أو إعادة التوجيه أو إضافة رؤوس أو تعيين ملفات تعريف الارتباط. يتم تشغيل البرمجيات الوسيطة قبل مطابقة المحتوى المخزن مؤقتًا والمسارات، مما يجعلها مثالية للمصادقة والترجمة واختبار A/B والمزيد.
ملاحظة: يتم تشغيل البرمجيات الوسيطة على Edge Runtime، مما يعني أنها تنفذ بالقرب من المستخدمين لديك لتحسين الأداء. ومع ذلك، ليست جميع واجهات برمجة تطبيقات Node.js متاحة في Edge Runtime، لذا كن واعيًا بواجهات برمجة التطبيقات التي تستخدمها.
إنشاء البرمجيات الوسيطة
يتم تعريف البرمجيات الوسيطة في ملف middleware.ts في جذر مشروعك (نفس مستوى دليل app أو pages):
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
console.log('تم تنفيذ البرمجيات الوسيطة لـ:', request.nextUrl.pathname);
return NextResponse.next();
}
تسجل هذه البرمجيات الوسيطة الأساسية كل طلب وتستمر إلى المعالج التالي. تتلقى دالة البرمجيات الوسيطة كائن NextRequest ويجب أن تُرجع NextResponse.
استجابات البرمجيات الوسيطة
يمكن للبرمجيات الوسيطة إرجاع أنواع مختلفة من الاستجابات:
NextResponse.next()
مواصلة خط أنابيب الطلب:
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// إضافة رؤوس مخصصة
response.headers.set('X-Custom-Header', 'MyValue');
return response;
}
NextResponse.redirect()
إعادة التوجيه إلى عنوان URL مختلف:
export function middleware(request: NextRequest) {
const isAuthenticated = request.cookies.get('auth-token');
if (!isAuthenticated && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
NextResponse.rewrite()
إعادة الكتابة داخليًا إلى عنوان URL مختلف دون تغيير عنوان URL في المتصفح:
export function middleware(request: NextRequest) {
// عرض صفحة الصيانة دون تغيير URL
if (process.env.MAINTENANCE_MODE === 'true') {
return NextResponse.rewrite(new URL('/maintenance', request.url));
}
return NextResponse.next();
}
NextResponse.json()
إرجاع استجابة JSON مباشرة من البرمجيات الوسيطة:
export function middleware(request: NextRequest) {
const apiKey = request.headers.get('x-api-key');
if (!apiKey) {
return NextResponse.json(
{ error: 'مفتاح API مطلوب' },
{ status: 401 }
);
}
return NextResponse.next();
}
تكوين المطابقات
بشكل افتراضي، يتم تشغيل البرمجيات الوسيطة على كل طلب. استخدم المطابقات لتحديد المسارات التي يجب أن تشغل البرمجيات الوسيطة:
تكوين المطابقة الأساسية
// middleware.ts
export function middleware(request: NextRequest) {
// منطق البرمجيات الوسيطة الخاص بك
return NextResponse.next();
}
// التشغيل فقط على مسارات محددة
export const config = {
matcher: '/dashboard/:path*'
};
مطابقات مسارات متعددة
export const config = {
matcher: [
'/dashboard/:path*',
'/admin/:path*',
'/api/:path*'
]
};
أنماط مطابقة معقدة
export const config = {
matcher: [
// مطابقة جميع مسارات الطلبات باستثناء تلك التي تبدأ بـ:
// - api (مسارات API)
// - _next/static (الملفات الثابتة)
// - _next/image (ملفات تحسين الصور)
// - favicon.ico (ملف الأيقونة)
'/((?!api|_next/static|_next/image|favicon.ico).*)',
]
};
المطابقات الشرطية
export const config = {
matcher: [
// مطابقة /about مع شرطة مائلة نهائية اختيارية
'/about/:path?',
// مطابقة /blog متبوعة بأي مسار
'/blog/:path+',
// مطابقة /product متبوعة بمقطع واحد على الأقل
'/product/:id+'
]
};
نصيحة: استخدم تكوين المطابقة لتحديد تنفيذ البرمجيات الوسيطة على المسارات التي تحتاج إليها فقط. هذا يحسن الأداء عن طريق تجنب تشغيلات البرمجيات الوسيطة غير الضرورية على الأصول الثابتة والمسارات العامة.
المصادقة مع البرمجيات الوسيطة
أحد أكثر حالات الاستخدام شيوعًا للبرمجيات الوسيطة هو حماية المسارات بالمصادقة:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token')?.value;
const isAuthPage = request.nextUrl.pathname.startsWith('/login');
const isProtectedRoute = request.nextUrl.pathname.startsWith('/dashboard');
// إعادة توجيه المستخدمين المصادق عليهم بعيدًا عن صفحات المصادقة
if (isAuthPage && token) {
return NextResponse.redirect(new URL('/dashboard', request.url));
}
// إعادة توجيه المستخدمين غير المصادق عليهم إلى تسجيل الدخول
if (isProtectedRoute && !token) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('from', request.nextUrl.pathname);
return NextResponse.redirect(loginUrl);
}
return NextResponse.next();
}
export const config = {
matcher: ['/dashboard/:path*', '/login']
};
التحقق من الرمز المميز
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { jwtVerify } from 'jose';
export async function middleware(request: NextRequest) {
const token = request.cookies.get('auth-token')?.value;
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
try {
// التحقق من رمز JWT
const secret = new TextEncoder().encode(process.env.JWT_SECRET);
const { payload } = await jwtVerify(token, secret);
// إضافة معلومات المستخدم إلى الرؤوس للمعالجات اللاحقة
const response = NextResponse.next();
response.headers.set('X-User-Id', payload.userId as string);
response.headers.set('X-User-Role', payload.role as string);
return response;
} catch (error) {
// رمز غير صالح، إعادة التوجيه إلى تسجيل الدخول
return NextResponse.redirect(new URL('/login', request.url));
}
}
export const config = {
matcher: '/dashboard/:path*'
};
الترجمة مع البرمجيات الوسيطة
قم بتنفيذ الترجمة الدولية عن طريق اكتشاف وإعادة التوجيه بناءً على لغة المستخدم:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const locales = ['en', 'ar', 'fr', 'es'];
const defaultLocale = 'en';
function getLocale(request: NextRequest): string {
// التحقق من معامل URL
const urlLocale = request.nextUrl.pathname.split('/')[1];
if (locales.includes(urlLocale)) {
return urlLocale;
}
// التحقق من ملف تعريف الارتباط
const cookieLocale = request.cookies.get('locale')?.value;
if (cookieLocale && locales.includes(cookieLocale)) {
return cookieLocale;
}
// التحقق من رأس Accept-Language
const acceptLanguage = request.headers.get('accept-language');
if (acceptLanguage) {
const browserLocale = acceptLanguage.split(',')[0].split('-')[0];
if (locales.includes(browserLocale)) {
return browserLocale;
}
}
return defaultLocale;
}
export function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname;
// التحقق مما إذا كان المسار يحتوي بالفعل على لغة
const pathnameHasLocale = locales.some(
(locale) => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
);
if (pathnameHasLocale) {
return NextResponse.next();
}
// إعادة التوجيه إلى URL مع بادئة اللغة
const locale = getLocale(request);
const response = NextResponse.redirect(
new URL(`/${locale}${pathname}`, request.url)
);
// تعيين ملف تعريف ارتباط اللغة للطلبات المستقبلية
response.cookies.set('locale', locale, { maxAge: 31536000 }); // سنة واحدة
return response;
}
export const config = {
matcher: [
// تخطي جميع المسارات الداخلية (_next، api، الملفات الثابتة)
'/((?!api|_next/static|_next/image|favicon.ico).*)',
]
};
اختبار A/B مع البرمجيات الوسيطة
قم بتنفيذ اختبار A/B عن طريق تعيين المستخدمين عشوائيًا لمتغيرات مختلفة:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// التحقق مما إذا كان المستخدم لديه بالفعل متغير معين
let variant = request.cookies.get('ab-test-variant')?.value;
if (!variant) {
// تعيين متغير A أو B عشوائيًا
variant = Math.random() < 0.5 ? 'A' : 'B';
}
const response = variant === 'B'
? NextResponse.rewrite(new URL('/variant-b', request.url))
: NextResponse.next();
// تخزين المتغير في ملف تعريف الارتباط للاتساق
response.cookies.set('ab-test-variant', variant, {
maxAge: 60 * 60 * 24 * 30 // 30 يومًا
});
// إضافة المتغير إلى الرؤوس للتحليلات
response.headers.set('X-AB-Test-Variant', variant);
return response;
}
export const config = {
matcher: '/landing'
};
تحديد المعدل مع البرمجيات الوسيطة
قم بتنفيذ تحديد المعدل الأساسي لمنع إساءة الاستخدام:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
// مخزن في الذاكرة (استخدم Redis في الإنتاج)
const rateLimitMap = new Map<string, { count: number; resetTime: number }>();
const RATE_LIMIT = 10; // طلبات
const WINDOW_MS = 60000; // دقيقة واحدة
export function middleware(request: NextRequest) {
const ip = request.ip || 'unknown';
const now = Date.now();
// الحصول على إدخال تحديد المعدل أو إنشاؤه
let rateLimit = rateLimitMap.get(ip);
if (!rateLimit || now > rateLimit.resetTime) {
// إنشاء نافذة تحديد معدل جديدة
rateLimit = {
count: 1,
resetTime: now + WINDOW_MS
};
rateLimitMap.set(ip, rateLimit);
} else {
// زيادة العدد
rateLimit.count++;
}
// التحقق مما إذا تم تجاوز حد المعدل
if (rateLimit.count > RATE_LIMIT) {
return NextResponse.json(
{ error: 'عدد كبير جدًا من الطلبات. يرجى المحاولة مرة أخرى لاحقًا.' },
{
status: 429,
headers: {
'Retry-After': String(Math.ceil((rateLimit.resetTime - now) / 1000))
}
}
);
}
// إضافة رؤوس تحديد المعدل
const response = NextResponse.next();
response.headers.set('X-RateLimit-Limit', String(RATE_LIMIT));
response.headers.set('X-RateLimit-Remaining', String(RATE_LIMIT - rateLimit.count));
response.headers.set('X-RateLimit-Reset', String(rateLimit.resetTime));
return response;
}
export const config = {
matcher: '/api/:path*'
};
تحذير: مثال تحديد المعدل في الذاكرة هذا مناسب للنشر أحادي المثيل فقط. للتطبيقات الإنتاجية ذات المثيلات المتعددة، استخدم ذاكرة تخزين مؤقت موزعة مثل Redis أو Edge Config من Vercel لتخزين بيانات تحديد المعدل.
تعيين رؤوس مخصصة
أضف رؤوسًا مخصصة إلى الاستجابات للأمان أو التحليلات أو علامات الميزات:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// رؤوس الأمان
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
// رؤوس مخصصة
response.headers.set('X-Request-Id', crypto.randomUUID());
response.headers.set('X-Server-Region', process.env.VERCEL_REGION || 'unknown');
// علامات الميزات
response.headers.set('X-Feature-NewUI', 'true');
return response;
}
اكتشاف وحظر الروبوتات
اكتشاف ومعالجة حركة مرور الروبوتات:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
const BOT_USER_AGENTS = [
'bot',
'crawler',
'spider',
'scraper',
'curl',
'wget'
];
export function middleware(request: NextRequest) {
const userAgent = request.headers.get('user-agent')?.toLowerCase() || '';
// التحقق مما إذا كان وكيل المستخدم يطابق أنماط الروبوت
const isBot = BOT_USER_AGENTS.some(pattern => userAgent.includes(pattern));
if (isBot) {
// يمكنك: حظر، إعادة توجيه، أو تقديم محتوى مختلف
// خيار 1: حظر
return NextResponse.json(
{ error: 'الوصول إلى الروبوت غير مسموح به' },
{ status: 403 }
);
// خيار 2: تقديم محتوى محسّن للروبوتات
// return NextResponse.rewrite(new URL('/bot-version', request.url));
// خيار 3: إضافة رأس للمعالجة اللاحقة
// const response = NextResponse.next();
// response.headers.set('X-Is-Bot', 'true');
// return response;
}
return NextResponse.next();
}
التوجيه القائم على الموقع الجغرافي
قم بتوجيه المستخدمين بناءً على موقعهم الجغرافي:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
// يوفر Vercel رؤوس جغرافية تلقائيًا
const country = request.geo?.country || 'US';
const city = request.geo?.city || 'Unknown';
// إعادة التوجيه بناءً على البلد
if (country === 'CN') {
return NextResponse.rewrite(new URL('/cn', request.url));
}
if (country === 'JP') {
return NextResponse.rewrite(new URL('/jp', request.url));
}
// إضافة معلومات جغرافية إلى الرؤوس
const response = NextResponse.next();
response.headers.set('X-User-Country', country);
response.headers.set('X-User-City', city);
return response;
}
ربط دوال البرمجيات الوسيطة المتعددة
قم بتنظيم منطق البرمجيات الوسيطة المعقد عن طريق ربط معالجات متعددة:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
type MiddlewareFactory = (
middleware: MiddlewareFunction
) => MiddlewareFunction;
type MiddlewareFunction = (
request: NextRequest
) => NextResponse | Promise<NextResponse>;
// دوال البرمجيات الوسيطة الفردية
function withAuth(middleware: MiddlewareFunction): MiddlewareFunction {
return async (request) => {
const token = request.cookies.get('auth-token');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
return middleware(request);
};
}
function withLogging(middleware: MiddlewareFunction): MiddlewareFunction {
return async (request) => {
console.log('طلب:', request.method, request.nextUrl.pathname);
const response = await middleware(request);
console.log('استجابة:', response.status);
return response;
};
}
function withHeaders(middleware: MiddlewareFunction): MiddlewareFunction {
return async (request) => {
const response = await middleware(request);
response.headers.set('X-Custom-Header', 'Value');
return response;
};
}
// ربط البرمجيات الوسيطة
function chain(
functions: MiddlewareFactory[],
index = 0
): MiddlewareFunction {
const current = functions[index];
if (current) {
const next = chain(functions, index + 1);
return current(next);
}
return () => NextResponse.next();
}
export default chain([withLogging, withAuth, withHeaders]);
export const config = {
matcher: '/dashboard/:path*'
};
تمرين: أنشئ نظام برمجيات وسيطة شاملاً يتضمن:
- فحص المصادقة لمسارات /admin و /dashboard
- التحكم في الوصول على أساس الدور (إعادة التوجيه بناءً على دور المستخدم)
- اكتشاف اللغة وإعادة التوجيه للترجمة الدولية
- تحديد المعدل لمسارات API
- رؤوس الأمان لجميع الاستجابات
- تسجيل الطلبات مع معرفات طلبات فريدة
استخدم تكوين المطابقة المناسب ونظم كودك بدوال قابلة لإعادة الاستخدام.
الملخص
البرمجيات الوسيطة في Next.js هي ميزة قوية للتعامل مع المخاوف الشاملة قبل وصول الطلبات إلى صفحاتك أو مسارات API. النقاط الرئيسية:
- يتم تشغيل البرمجيات الوسيطة على Edge Runtime للحصول على أداء مثالي
- حدد البرمجيات الوسيطة في ملف middleware.ts في جذر المشروع
- استخدم المطابقات للتحكم في المسارات التي تشغل البرمجيات الوسيطة
- حالات الاستخدام الشائعة: المصادقة، الترجمة، اختبار A/B، تحديد المعدل
- أرجع NextResponse.next() أو redirect() أو rewrite() أو json() بناءً على احتياجاتك
- الوصول إلى خصائص الطلب مثل ملفات تعريف الارتباط والرؤوس والمعلومات الجغرافية
- قم بتعديل الاستجابات عن طريق إضافة رؤوس أو تعيين ملفات تعريف الارتباط
- قم بربط دوال البرمجيات الوسيطة المتعددة للمنطق المعقد