الأدوات الوسطية في Next.js

من موسوعة حسوب

تُمكِّنك الأدوات أو البرمجيات الوسيطة MiddleWare من تنفيذ مهمة قبل اكتمال كل طلب request ثم بناءً على الطلب الوارد، يمكنك تعديل الرد من خلال إعادة كتابة أو إعادة التوجيه أو إضافة الترويسات أو حتى بالرد على الطلب مباشرة.

تُنفَّذ البرمجيات الوسيطة قبل المحتوى المخزَّن مؤقتًا cached content لذا يمكنك تخصيص الملفات والصفحات الساكنة، وأشهر استخدامات البرمجيات الوسيطة هي في الاستيثاق authentication أو اختبار A/B أو تخصيص الصفحات وفقًا للمناطق الزمنة (عبر بدء استخدام التوجيه عبر i18n) أو في الحماية من الشيفرات الآلية أو غيرها حتى الاستخدامات المتقدمة.

ملاحظة: إن كنت تستخدم البرمجيات الوسيطة قبل الإصدار 12.2، فارجع إلى دليل الترقية.

سجل التغييرات

النسخة التغييرات
13.1.0 أضيفت رايات flags متقدمة
13.0.0 تستطيع الآن البرمجيات الوسيطة تعديل ترويسات الطلب request headers وترويسات الرد response headers وإرسال الردود
12.2.0 استقرار دعم البرمجيات الوسيطة
12.0.9 إلزامية استخدام العناوين المطلقة أثناء تشغيل التطبيق بالاستفادة من الحوسبة الحدية Edge
12.0.0 دعم البرمجيات الوسطية (تجريبيًا)

استخدام البرمجيات الوسطية Middleware

ثبّت النسخة الأحدث من Next.js:

npm install next@latest

أنشئ الملف middleware.ts ضمن جذر مجلد مشروعك أو ضمن المجلد src بجوار المجلد pages ثم صدِّر دالة الأداة الوسطية من الملف middleware.ts.

// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

//`await` إن كنت تستخدم `async` يمكن أن تجعل هذه الدالة 
export function middleware(request: NextRequest) {
  return NextResponse.redirect(new URL('/about-2', request.url))
}
// انظر إلى "المسارات المطابقة" في الأسفل لتعرف أكثر
// See "Matching Paths" below to learn more
export const config = {
  matcher: '/about/:path*',
}

المسارات المطابقة

ستُستخدم البرمجيات الوسيطة في كل وجهات مشروعك وفق ترتيب التنفيذ التالي:

  1. headers من next.config.js
  2. redirects من next.config.js
  3. مع (rewrites و redirects وغيرها)
  4. beforeFiles (rewrites) من next.config.js
  5. التنقل في نظام المفات (public/ و ‎_next/static/‎و الصفحات وغيرها.)
  6. afterFiles (rewrites) من next.config.js
  7. الوجهات الديناميكية (‎/blog/[slug])
  8. fallback (rewrites) من next.config.js

توجد طريقتين لتعريف المسار الذي تُنفّذ عليه البرمجيات الوسيطة:

  1. تهيئة مُطابق مخصص custom matchers.
  2. العبارات الشرطية.

المطابقات

يسمح المطابق matcher بفلترة البرمجيات الوسيطة لتعمل على مسارات محددة:

export const config = {
  matcher: '/about/:path*',
}

يمكنك مطابقة مسار واحد أو عدة مسارات باستخدام المصفوفات:

export const config = {
  matcher: ['/about/:path*', '/dashboard/:path*'],
}

يُسمح بالاستخدام الكامل للتعابير النمطية regex عند تهيئة المطابق matcher مثل أسلوب البحث السلبي قدمًا negative lookahead أو مطابقة المحارف. وكمثال عن البحث السلبي قُدمًا لمطابقة كل المسارات المخصصة المحتملة نجد:

export const config = {
  matcher: [
    /*
     *طابق كل المسارات المطلوبة ما عدا التي تبدأ بـ 
     * - api (ِAPI وجهات)
     * - _next/static (ملفات ساكنة)
     * - favicon.ico (favicon الملف)
     */
    '/((?!api|_next/static|favicon.ico).*)',
  ],
}

ملاحظة: يجب أن تكون قيم المطابق ثابتة كي تُحلل بشكل ساكن أثناء بناء التطبيق، وسيجري تجاهل القيم الديناميكية مثل المتغيرات.

لتهيئة المطابقات:

  1. يجب أن تبدأ بالمحرف /.
  2. يمكن أن تضم معاملات مسمّاة مثل about/:path/ التي قد تطابق about/a/ و about/b/ لكن لن تطابق about/a/c/.
  3. يمكن أن تضم تغييرات على المعاملات المسمّاة (تبدأ بـ :) مثل *about/:path/ الذي يطابق about/a/b/c/ لأن * تعني "صفر أو أكثر" و ? "صفر أو واحد" و + "واحد أو أكثر".
  4. يمكن استخدام التعابير النمطية ضمن أقواس مثل (*.)/about/ بدلًا من about/:path/*.

ملاحظة: لأغراض التوافق تعُدّ Next.js المسار public/ هو نفسه public/index/ وبالتالي سيعثر عليه المُطابق public/:path/.

العبارات الشرطية

// middleware.ts

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))
  }
}

الواجهة البرمجية NextResponse

تسمح هذه الواجهة بالأمور التالية:

  • إعادة توجيه redirect الطلب القادم إلى عنوان URL مختلف.
  • إعادة كتابة rewrite (الكتابة فوق) الاستجابة عن طريق عرض عنوان URL محدد.
  • ضبط ترويسات الطلب لاستخدام وجهات API والدالة getServerSideProps والدالة rewrite لإعادة كتابة الوجهات النهائية.
  • ضبط ملفات تعريف الارتباط cookies الخاصة بالاستجابة.
  • ضبط ترويسات الاستجابة.

لتوليد استجابة عن البرمجيات الوسيطة، عليك تنفيذ على صفحة أو وجهة API حديّة قادرة على توليد استجابة.

استخدام ملفات تعريف الارتباط

تُعد ملفات تعريف الارتباط ترويسات نمطية. إذ تُخزّن هذه الملفات في الترويسة Cookie الخاصة بالطلب Request. بينما تخزن ملفات تعريف الارتباط في الترويسة Set-Cookie في الاستجابة Response. تزوّدك بطريقة ملائمة للوصول إلى ملفات تعريف الارتباط من خلال الموسِّع cookies الذي يُستخدم مع NextRequest و NextResponse.

  1. يأتي الموسّع في حالة الطلبات المستقبلة مع التوابع get و getAll و set و delete، كما يمكنك التحقق من وجود ملف تعريف ارتباط من خلال التابع has وإزالته باستخدام التابع clear.
  2. يأتي الموسِّع في حالة الطلبات المرسلة مع التوابع get و getAll و set و delete.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  //ضمن طلب وارد "Cookie:nextjs=fast" افترض ترويسة ملف تعريف ارتباط 
  //RequestCookies للحصول على ملفات تعريف الارتباط من الطلب باستخدام 
  const cookie = request.cookies.get('nextjs')?.value
  console.log(cookie) // => 'fast'
  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

  //RequestCookies ضبط ملفات تعريف الارتباط باستخدام  
    const response = NextResponse.next()
  response.cookies.set('vercel', 'fast')
  response.cookies.set({
    name: 'vercel',
    value: 'fast',
    path: '/test',
  })
  const cookie = response.cookies.get('vercel')
  console.log(cookie) // => { name: 'vercel', value: 'fast', Path: '/test' }
  //`Set-Cookie:vercel=fast;path=/test` ستمتلك الاستجابة المرسلة الترويسة

  return response
}

ضبط الترويسات

بالإمكان ضبط ترويسات الطلب والاستجابة باستخدام الواجهة البرمجية NextResponse (ضبط توريسات الطلب متاح في النسخة 13 ومابعد):

// middleware.ts

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
}

ملاحظة: تحاشى ضبط ترويسات كبيرة الحجم لانها قد تسبب الخطأ "431 Request Header Fields Too Large" تبعًا لإعدادات الواجهة الخلفية للتطبيق على خادم الويب.

توليد استجابة في Next.js

يمكنك الاستجابة للبرمجيات الوسيطة مباشرة بإعادة الكائن NextResponse (متاحة ابتداءً من النسخة 13). وعند تمكين هذه الميزة، ستتمكن من الحصول على استجابة من البرمجيات الوسيطة باستخدم الواجهتين Response أو NextResponse.

// middleware.ts
import { NextRequest, NextResponse } from 'next/server'
import { isAuthenticated } from '@lib/auth'

// `/api/` حصر استخام البرمجيات الوسيطة بالمسارات التي تبدأ بـ  
export const config = {
  matcher: '/api/:function*',
}

export function middleware(request: NextRequest) {
  //طلب الاستيثاق للتحقق من الطلب 
  if (!isAuthenticated(request)) {
    //للإشارة إلى خطأالاستيثاق JSON الاستجابة باستخدام  
      return new NextResponse(
      JSON.stringify({ success: false, message: 'authentication failed' }),
      { status: 401, headers: { 'content-type': 'application/json' } }
    )
  }
}

رايات متقدمة للبرمجات الوسيطة

أضيفت رايتان جديدتان إلى البرمجيات الوسيطة في النسخة 13.1 من Next.js هما :skipMiddlewareUrlNormalize و skipTrailingSlashRedirect، وذلك للتعامل مع بعض الحالات المتقدمة.

تتيح الراية skipTrailingSlashRedirect إيقاف التوجيه الافتراضي في Next.js لإضافة أو إزالة المحارف / السابقة للوجهة من أجل تخصيص طريقة العمل معها داخل البرمجيات الوسيطة التي تسمح بإبقاء تلك المحارف لبعض المسارات فقط لتسهيل عملية التهجير التدريجي incremental migrations.

// next.config.js
module.exports = {
  skipTrailingSlashRedirect: true,
}
// middleware.js

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 الأصلية التي تتيح هذه الراية استخدامها.

// next.config.js

module.exports = {
  skipMiddlewareUrlNormalize: true,
}
// middleware.js

export default async function middleware(req) {
  const { pathname } = req.nextUrl

  // /_next/data/build-id/hello.json الحصول على  

  console.log(pathname)
  // /_next/data/build-id/hello.json تصبح مع الراية على الشكل  
  // /hello بينما ستكون دون هذه الراية على الشكل 
}

اقرأ أيضًا

المصادر

  • الصفحة middleware من توثيق Next.js الرسمي.