البرمجيات الوسيطة (Middleware)
يسمح لك Middleware بتشغيل كود قبل اكتمال الطلب. ثم بناءً على الطلب الوارد، يمكنك تعديل الاستجابة عن طريق إعادة الكتابة، إعادة التوجيه، تعديل رؤوس الطلب أو الاستجابة، أو الرد مباشرة.
يعمل Middleware قبل مطابقة المحتوى المخزن مؤقتًا والمسارات. راجع مطابقة المسارات لمزيد من التفاصيل.
حالات الاستخدام
يمكن لدمج Middleware في تطبيقك أن يؤدي إلى تحسينات كبيرة في الأداء والأمان وتجربة المستخدم. بعض السيناريوهات الشائعة حيث يكون Middleware فعالاً بشكل خاص تشمل:
- المصادقة والتفويض: التأكد من هوية المستخدم والتحقق من ملفات تعريف الارتباط للجلسة قبل منح الوصول إلى صفحات أو مسارات API محددة.
- إعادة التوجيه من جانب الخادم: إعادة توجيه المستخدمين على مستوى الخادم بناءً على شروط معينة (مثل اللغة، دور المستخدم).
- إعادة كتابة المسار: دعم اختبار A/B، طرح الميزات أو المسارات القديمة عن طريق إعادة كتابة المسارات ديناميكيًا لمسارات API أو الصفحات بناءً على خصائص الطلب.
- كشف الروبوتات: حماية مواردك عن طريق كشف وحظر حركة مرور الروبوتات.
- التسجيل والتحليلات: التقاط وتحليل بيانات الطلب للرؤى قبل المعالجة بواسطة الصفحة أو API.
- إدارة الميزات: تمكين أو تعطيل الميزات ديناميكيًا لطرح الميزات أو الاختبار بسلاسة.
من المهم بنفس القدر التعرف على المواقف التي قد لا يكون فيها Middleware هو الحل الأمثل. إليك بعض السيناريوهات التي يجب الانتباه إليها:
- جلب البيانات المعقدة ومعالجتها: Middleware غير مصمم لجلب البيانات أو معالجتها مباشرة، يجب القيام بذلك ضمن Route Handlers أو أدوات جانب الخادم بدلاً من ذلك.
- المهام الحسابية الثقيلة: يجب أن يكون Middleware خفيف الوزن ويستجيب بسرعة وإلا يمكن أن يتسبب في تأخير تحميل الصفحة. يجب تنفيذ المهام الحسابية الثقيلة أو العمليات طويلة المدى ضمن Route Handlers مخصصة.
- إدارة الجلسات المكثفة: بينما يمكن لـ Middleware إدارة مهام الجلسات الأساسية، يجب إدارة إدارة الجلسات المكثفة بواسطة خدمات المصادقة المخصصة أو ضمن Route Handlers.
- عمليات قاعدة البيانات المباشرة: لا يوصى بإجراء عمليات قاعدة بيانات مباشرة ضمن Middleware. يجب تنفيذ تفاعلات قاعدة البيانات ضمن Route Handlers أو أدوات جانب الخادم.
الاتفاقية
استخدم ملف middleware.ts
(أو .js
) في جذر مشروعك لتعريف Middleware. على سبيل المثال، في نفس مستوى pages
أو app
، أو داخل src
إذا كان ذلك مناسبًا.
ملاحظة: بينما يتم دعم ملف
middleware.ts
واحد فقط لكل مشروع، لا يزال بإمكانك تنظيم منطق Middleware الخاص بك بشكل معياري. قسّم وظائف Middleware إلى ملفات.ts
أو.js
منفصلة واستوردها في ملفmiddleware.ts
الرئيسي الخاص بك. هذا يسمح بإدارة أنظف لـ Middleware الخاص بالمسار، مجمّع فيmiddleware.ts
للتحكم المركزي. من خلال فرض ملف Middleware واحد، يبسط التكوين، يمنع النزاعات المحتملة، ويحسن الأداء عن طريق تجنب طبقات متعددة من Middleware.
مثال
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// يمكن وضع علامة `async` على هذه الدالة إذا تم استخدام `await` داخلها
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url))
}
// راجع "مطابقة المسارات" أدناه لمعرفة المزيد
export const config = {
matcher: '/about/:path*',
}
import { NextResponse } from 'next/server'
// يمكن وضع علامة `async` على هذه الدالة إذا تم استخدام `await` داخلها
export function middleware(request) {
return NextResponse.redirect(new URL('/home', request.url))
}
// راجع "مطابقة المسارات" أدناه لمعرفة المزيد
export const config = {
matcher: '/about/:path*',
}
مطابقة المسارات
سيتم استدعاء Middleware لكل مسار في مشروعك. بالنظر إلى هذا، من الضروري استخدام matchers لاستهداف أو استبعاد مسارات محددة بدقة. فيما يلي ترتيب التنفيذ:
headers
منnext.config.js
redirects
منnext.config.js
- Middleware (
rewrites
,redirects
, إلخ.) beforeFiles
(rewrites
) منnext.config.js
- مسارات نظام الملفات (
public/
,_next/static/
,pages/
,app/
, إلخ.) afterFiles
(rewrites
) منnext.config.js
- المسارات الديناميكية (
/blog/[slug]
) fallback
(rewrites
) منnext.config.js
هناك طريقتان لتعريف المسارات التي سيعمل عليها Middleware:
Matcher
يسمح لك matcher
بتصفية Middleware ليعمل على مسارات محددة.
export const config = {
matcher: '/about/:path*',
}
يمكنك مطابقة مسار واحد أو عدة مسارات باستخدام صيغة المصفوفة:
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*'],
}
يسمح تكوين matcher
باستخدام regex كامل لذا فإن المطابقة مثل negative lookaheads أو مطابقة الأحرف مدعومة. يمكن رؤية مثال على negative lookahead لمطابقة كل المسارات باستثناء مسارات محددة هنا:
export const config = {
matcher: [
/*
* مطابقة جميع مسارات الطلب باستثناء تلك التي تبدأ بـ:
* - api (مسارات API)
* - _next/static (ملفات ثابتة)
* - _next/image (ملفات تحسين الصور)
* - favicon.ico (ملف favicon)
*/
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
}
يمكنك أيضًا تخطي Middleware لطلبات معينة باستخدام مصفوفات missing
أو has
، أو مزيج من الاثنين:
export const config = {
matcher: [
/*
* مطابقة جميع مسارات الطلب باستثناء تلك التي تبدأ بـ:
* - api (مسارات API)
* - _next/static (ملفات ثابتة)
* - _next/image (ملفات تحسين الصور)
* - favicon.ico (ملف favicon)
*/
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
missing: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
has: [
{ type: 'header', key: 'next-router-prefetch' },
{ type: 'header', key: 'purpose', value: 'prefetch' },
],
},
{
source: '/((?!api|_next/static|_next/image|favicon.ico).*)',
has: [{ type: 'header', key: 'x-present' }],
missing: [{ type: 'header', key: 'x-missing', value: 'prefetch' }],
},
],
}
جيد لمعرفته: يجب أن تكون قيم
matcher
ثوابت حتى يمكن تحليلها بشكل ثابت في وقت البناء. سيتم تجاهل القيم الديناميكية مثل المتغيرات.
تكوينات matcher:
- يجب أن تبدأ بـ
/
- يمكن أن تتضمن معلمات مسماة:
/about/:path
تطابق/about/a
و/about/b
ولكن ليس/about/a/c
- يمكن أن تحتوي على معدلات على المعلمات المسماة (تبدأ بـ
:
):/about/:path*
تطابق/about/a/b/c
لأن*
تعني صفر أو أكثر.?
تعني صفر أو واحد و+
تعني واحد أو أكثر - يمكن استخدام تعبير عادي محاط بأقواس:
/about/(.*)
هو نفس/about/:path*
اقرأ المزيد من التفاصيل في وثائق path-to-regexp.
جيد لمعرفته: لأغراض التوافق مع الإصدارات السابقة، يعتبر Next.js دائمًا
/public
كـ/public/index
. لذلك، فإن matcher لـ/public/:path
سيطابق.
عبارات شرطية
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
import { NextResponse } from 'next/server'
export function middleware(request) {
if (request.nextUrl.pathname.startsWith('/about')) {
return NextResponse.rewrite(new URL('/about-2', request.url))
}
if (request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.rewrite(new URL('/dashboard/user', request.url))
}
}
NextResponse
يسمح لك واجهة برمجة التطبيقات NextResponse
بما يلي:
redirect
الطلب الوارد إلى عنوان URL مختلفrewrite
الاستجابة عن طريق عرض عنوان URL معين- تعيين رؤوس الطلب لمسارات API،
getServerSideProps
، وأوجه إعادة الكتابة - تعيين ملفات تعريف الارتباط للاستجابة
- تعيين رؤوس الاستجابة
لإنتاج استجابة من Middleware، يمكنك:
rewrite
إلى مسار (صفحة أو Edge API Route) ينتج استجابة- إرجاع
NextResponse
مباشرة. راجع إنتاج استجابة
استخدام ملفات تعريف الارتباط
ملفات تعريف الارتباط هي رؤوس عادية. في Request
، يتم تخزينها في رأس Cookie
. في Response
تكون في رأس Set-Cookie
. يوفر Next.js طريقة ملائمة للوصول إلى ملفات تعريف الارتباط هذه ومعالجتها من خلال امتداد cookies
على NextRequest
و NextResponse
.
- للطلبات الواردة، يأتي
cookies
مع الطرق التالية:get
،getAll
،set
، وdelete
لملفات تعريف الارتباط. يمكنك التحقق من وجود ملف تعريف ارتباط باستخدامhas
أو إزالة جميع ملفات تعريف الارتباط باستخدامclear
. - للاستجابات الصادرة، تحتوي
cookies
على الطرق التاليةget
،getAll
،set
، وdelete
.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// افترض وجود رأس "Cookie:nextjs=fast" في الطلب الوارد
// الحصول على ملفات تعريف الارتباط من الطلب باستخدام واجهة برمجة التطبيقات `RequestCookies`
let cookie = request.cookies.get('nextjs')
console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
const allCookies = request.cookies.getAll()
console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
request.cookies.has('nextjs') // => true
request.cookies.delete('nextjs')
request.cookies.has('nextjs') // => false
// تعيين ملفات تعريف الارتباط على الاستجابة باستخدام واجهة برمجة التطبيقات `ResponseCookies`
const response = NextResponse.next()
response.cookies.set('vercel', 'fast')
response.cookies.set({
name: 'vercel',
value: 'fast',
path: '/',
})
cookie = response.cookies.get('vercel')
console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
// سيكون للاستجابة الصادرة رأس `Set-Cookie:vercel=fast;path=/`.
return response
}
import { NextResponse } from 'next/server'
export function middleware(request) {
// افترض وجود رأس "Cookie:nextjs=fast" في الطلب الوارد
// الحصول على ملفات تعريف الارتباط من الطلب باستخدام واجهة برمجة التطبيقات `RequestCookies`
let cookie = request.cookies.get('nextjs')
console.log(cookie) // => { name: 'nextjs', value: 'fast', Path: '/' }
const allCookies = request.cookies.getAll()
console.log(allCookies) // => [{ name: 'nextjs', value: 'fast' }]
request.cookies.has('nextjs') // => true
request.cookies.delete('nextjs')
request.cookies.has('nextjs') // => false
// تعيين ملفات تعريف الارتباط على الاستجابة باستخدام واجهة برمجة التطبيقات `ResponseCookies`
const response = NextResponse.next()
response.cookies.set('vercel', 'fast')
response.cookies.set({
name: 'vercel',
value: 'fast',
path: '/',
})
cookie = response.cookies.get('vercel')
console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/' }
// سيكون للاستجابة الصادرة رأس `Set-Cookie:vercel=fast;path=/test`.
return response
}
تعيين الرؤوس
يمكنك تعيين رؤوس الطلب والاستجابة باستخدام واجهة برمجة التطبيقات NextResponse
(تعيين رؤوس الطلب متاح منذ Next.js v13.0.0).
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// استنساخ رؤوس الطلب وتعيين رأس جديد `x-hello-from-middleware1`
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')
// يمكنك أيضًا تعيين رؤوس الطلب في NextResponse.rewrite
const response = NextResponse.next({
request: {
// رؤوس طلب جديدة
headers: requestHeaders,
},
})
// تعيين رأس استجابة جديد `x-hello-from-middleware2`
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
import { NextResponse } from 'next/server'
export function middleware(request) {
// استنساخ رؤوس الطلب وتعيين رأس جديد `x-hello-from-middleware1`
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')
// يمكنك أيضًا تعيين رؤوس الطلب في NextResponse.rewrite
const response = NextResponse.next({
request: {
// رؤوس طلب جديدة
headers: requestHeaders,
},
})
// تعيين رأس استجابة جديد `x-hello-from-middleware2`
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}
جيد لمعرفته: تجنب تعيين رؤوس كبيرة لأنها قد تسبب خطأ 431 Request Header Fields Too Large اعتمادًا على تكوين خادم الويب الخلفي الخاص بك.
CORS (مشاركة الموارد عبر المصادر)
يمكنك تعيين رؤوس CORS في Middleware للسماح بطلبات المصادر المتقاطعة، بما في ذلك الطلبات البسيطة والطلبات المسبقة.
import { NextRequest, NextResponse } from 'next/server'
const allowedOrigins = ['https://acme.com', 'https://my-app.org']
const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
export function middleware(request: NextRequest) {
// Check the origin from the request
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)
// Handle preflighted requests
const isPreflight = request.method === 'OPTIONS'
if (isPreflight) {
const preflightHeaders = {
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
...corsOptions,
}
return NextResponse.json({}, { headers: preflightHeaders })
}
// Handle simple requests
const response = NextResponse.next()
if (isAllowedOrigin) {
response.headers.set('Access-Control-Allow-Origin', origin)
}
Object.entries(corsOptions).forEach(([key, value]) => {
response.headers.set(key, value)
})
return response
}
export const config = {
matcher: '/api/:path*',
}
import { NextResponse } from 'next/server'
const allowedOrigins = ['https://acme.com', 'https://my-app.org']
const corsOptions = {
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}
export function middleware(request) {
// Check the origin from the request
const origin = request.headers.get('origin') ?? ''
const isAllowedOrigin = allowedOrigins.includes(origin)
// Handle preflighted requests
const isPreflight = request.method === 'OPTIONS'
if (isPreflight) {
const preflightHeaders = {
...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
...corsOptions,
}
return NextResponse.json({}, { headers: preflightHeaders })
}
// Handle simple requests
const response = NextResponse.next()
if (isAllowedOrigin) {
response.headers.set('Access-Control-Allow-Origin', origin)
}
Object.entries(corsOptions).forEach(([key, value]) => {
response.headers.set(key, value)
})
return response
}
export const config = {
matcher: '/api/:path*',
}
إنتاج استجابة
يمكنك الرد من Middleware مباشرة عن طريق إرجاع نسخة من Response
أو NextResponse
. (هذا متاح منذ Next.js v13.1.0)
import { NextRequest } from 'next/server'
import { isAuthenticated } from '@lib/auth'
// Limit the middleware to paths starting with `/api/`
export const config = {
matcher: '/api/:function*',
}
export function middleware(request: NextRequest) {
// Call our authentication function to check the request
if (!isAuthenticated(request)) {
// Respond with JSON indicating an error message
return Response.json(
{ success: false, message: 'authentication failed' },
{ status: 401 }
)
}
}
import { isAuthenticated } from '@lib/auth'
// Limit the middleware to paths starting with `/api/`
export const config = {
matcher: '/api/:function*',
}
export function middleware(request) {
// Call our authentication function to check the request
if (!isAuthenticated(request)) {
// Respond with JSON indicating an error message
return Response.json(
{ success: false, message: 'authentication failed' },
{ status: 401 }
)
}
}
waitUntil
و NextFetchEvent
كائن NextFetchEvent
يمتد من الكائن الأصلي FetchEvent
، ويتضمن طريقة waitUntil()
.
تأخذ طريقة waitUntil()
وعدًا (promise) كوسيطة، وتوسع عمر Middleware حتى يتم تسوية الوعد. هذا مفيد لأداء العمل في الخلفية.
import { NextResponse } from 'next/server'
import type { NextFetchEvent, NextRequest } from 'next/server'
export function middleware(req: NextRequest, event: NextFetchEvent) {
event.waitUntil(
fetch('https://my-analytics-platform.com', {
method: 'POST',
body: JSON.stringify({ pathname: req.nextUrl.pathname }),
})
)
return NextResponse.next()
}
أعلام Middleware المتقدمة
في الإصدار v13.1
من Next.js، تم إدخال علامتين إضافيتين لـ Middleware، وهما skipMiddlewareUrlNormalize
و skipTrailingSlashRedirect
للتعامل مع حالات الاستخدام المتقدمة.
skipTrailingSlashRedirect
تعطيل عمليات إعادة التوجيه في Next.js لإضافة أو إزالة الشرطة المائلة في النهاية. هذا يسمح بالتعامل المخصص داخل Middleware للحفاظ على الشرطة المائلة لبعض المسارات وليس غيرها، مما يمكن أن يجعل عمليات الترحيل التدريجي أسهل.
module.exports = {
skipTrailingSlashRedirect: true,
}
const legacyPrefixes = ['/docs', '/blog']
export default async function middleware(req) {
const { pathname } = req.nextUrl
if (legacyPrefixes.some((prefix) => pathname.startsWith(prefix))) {
return NextResponse.next()
}
// apply trailing slash handling
if (
!pathname.endsWith('/') &&
!pathname.match(/((?!\.well-known(?:\/.*)?)(?:[^/]+\/)*[^/]+\.\w+)/)
) {
req.nextUrl.pathname += '/'
return NextResponse.redirect(req.nextUrl)
}
}
skipMiddlewareUrlNormalize
تسمح بتعطيل تطبيع URL في Next.js لجعل التعامل مع الزيارات المباشرة والانتقالات من العميل متشابهة. في بعض الحالات المتقدمة، توفر هذه الخيار تحكمًا كاملاً باستخدام URL الأصلي.
module.exports = {
skipMiddlewareUrlNormalize: true,
}
export default async function middleware(req) {
const { pathname } = req.nextUrl
// GET /_next/data/build-id/hello.json
console.log(pathname)
// with the flag this now /_next/data/build-id/hello.json
// without the flag this would be normalized to /hello
}
وقت التشغيل
يدعم Middleware حاليًا فقط وقت تشغيل Edge. لا يمكن استخدام وقت تشغيل Node.js.
سجل الإصدارات
الإصدار | التغييرات |
---|---|
v13.1.0 | تمت إضافة أعلام Middleware المتقدمة |
v13.0.0 | يمكن لـ Middleware تعديل رؤوس الطلب، ورؤوس الاستجابة، وإرسال الردود |
v12.2.0 | أصبح Middleware مستقرًا، يرجى الاطلاع على دليل الترقية |
v12.0.9 | فرض عناوين URL المطلقة في وقت تشغيل Edge (PR) |
v12.0.0 | تمت إضافة Middleware (بيتا) |