میان افزار
میان افزار به شما امکان میدهد قبل از تکمیل درخواست، کد را اجرا کنید. سپس، بر اساس درخواست ورودی، میتوانید با بازنویسی، تغییر مسیر، تغییر سرصفحههای درخواست یا پاسخ، یا پاسخ مستقیم، پاسخ را تغییر دهید.
میان افزار قبل از محتوای کش شده و مطابقت با مسیرها اجرا میشود. برای جزئیات بیشتر Matching Paths را ببینید.
موارد استفاده
ادغام میان افزار در برنامه شما میتواند منجر به بهبود قابل توجهی در عملکرد، امنیت و تجربه کاربری شود. برخی از سناریوهای رایج که میان افزار در آنها به خصوص مؤثر است عبارتند از:
- احراز هویت و مجوز: قبل از اعطای دسترسی به صفحات یا مسیرهای API خاص، از هویت کاربر اطمینان حاصل کنید و کوکیهای جلسه را بررسی کنید.
- تغییر مسیر سمت سرور: کاربران را بر اساس شرایط خاص (به عنوان مثال، محل locale، user role نقش کاربر) در سطح سرور هدایت کنید.
- بازنویسی مسیر: با بازنویسی مسیرها به صورت داینامیک به مسیرهای API یا صفحات بر اساس ویژگیهای درخواست، از تست A/B، راهاندازی ویژگیها یا مسیرهای قدیمی پشتیبانی کنید.
- تشخیص ربات: با شناسایی و مسدود کردن ترافیک ربات، از منابع خود محافظت کنید.
- ورود به سیستم و تجزیه و تحلیل: قبل از پردازش توسط صفحه یا API، دادههای درخواست را برای بینش ثبت و تجزیه و تحلیل کنید.
- پرچم گذاری ویژگی: برای راهاندازی یا تست ویژگیهای بدون دردسر، ویژگیها را به صورت داینامیک فعال یا غیرفعال کنید.
تشخیص موقعیت هایی که ممکن است میان افزار بهترین روش نباشد به همان اندازه مهم است. در اینجا چند سناریو که باید به آنها توجه کنید آورده شده است:
- بازیابی و دستکاری دادههای پیچیده: میان افزار برای بازیابی یا دستکاری مستقیم داده طراحی نشده است، این کار باید به جای آن در هندلر مسیر ها یا ابزارهای سمت سرور انجام شود.
- کارهای محاسباتی سنگین: میان افزار باید سبک وزن باشد و به سرعت پاسخ دهد، در غیر این صورت میتواند باعث تأخیر در بارگذاری صفحه شود. وظایف محاسباتی سنگین یا فرآیندهای طولانی مدت باید در هندلر مسیر های اختصاصی انجام شود.
- مدیریت گسترده جلسه: در حالی که میان افزار میتواند وظایف اولیه جلسه را مدیریت کند، مدیریت گسترده جلسه باید توسط سرویسهای احراز هویت اختصاصی یا درون هندلر مسیر ها انجام شود.
- عملیات مستقیم پایگاه داده: انجام عملیات مستقیم پایگاه داده در میان افزار توصیه نمی شود. تعامل با پایگاه داده باید در هندلر مسیر ها یا ابزارهای سمت سرور انجام شود.
قرارداد
از فایل middleware.ts (یا .js) در ریشه پروژه خود برای تعریف میان افزار استفاده کنید. برای مثال، در همان سطح pages یا app، یا در صورت وجود داخل src.
نکته: در حالی که تنها یک فایل
middleware.tsبه ازای هر پروژه پشتیبانی میشود، همچنان میتوانید منطق میان افزار خود را به صورت modular سازماندهی کنید. عملکردهای میان افزار را به فایلهای جداگانه.tsیا.jsتقسیم کرده و آنها را به فایل اصلیmiddleware.tsخود وارد کنید. این امکان مدیریت تمیزتر میان افزار های خاص مسیر را فراهم می کند که درmiddleware.tsبرای کنترل متمرکز جمع آوری شده اند. با اجباری کردن یک فایل میان افزار پیکربندی را ساده میکند، از تداخلهای بالقوه جلوگیری میکند و با اجتناب از لایههای متعدد میان افزار، عملکرد را بهینه میکند.
مثال
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url))
}
// See "Matching Paths" below to learn more
export const config = {
matcher: '/about/:path*',
}مطابقت مسیرها
میان افزار برای هر مسیر در پروژه شما فراخوانده میشود. با توجه به این، استفاده از تطبیقدهندهها (matcher) برای هدفگذاری دقیق یا حذف مسیرهای خاص بسیار مهم است. ترتیب اجرای موارد به شرح زیر است:
headersfromnext.config.jsredirectsfromnext.config.js- Middleware (
rewrites,redirects, etc.) beforeFiles(rewrites) fromnext.config.js- Filesystem routes (
public/,_next/static/,pages/,app/, etc.) afterFiles(rewrites) fromnext.config.js- Dynamic Routes (
/blog/[slug]) fallback(rewrites) fromnext.config.js
دو راه برای تعریف اینکه میان افزار روی کدام مسیرها اجرا شود وجود دارد:
تطبیقدهنده Matcher
matcher به شما امکان میدهد میان افزار را برای اجرا در مسیرهای خاص فیلتر کنید.
export const config = {
matcher: '/about/:path*',
}شما میتوانید با استفاده از سینتکس آرایه، یک مسیر یا چندین مسیر را مطابقت دهید:
export const config = {
matcher: ['/about/:path*', '/dashboard/:path*'],
}پیکربندی matcher regex کامل را امکان پذیر می کند، بنابراین مطابقتهایی مانند lookaheadهای منفی یا مطابقت کاراکتر پشتیبانی میشود. نمونهای از یک negative lookahead برای مطابقت با همه مسیرها به جز مسیرهای خاص را میتوانید در اینجا ببینید:
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
}همچنین میتوانید با استفاده از آرایههای missing یا has یا ترکیبی از هر دو، میان افزار را برای درخواستهای خاص دور بزنید.
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
{
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باید ثابت باشند تا بتوان آنها را در زمان ساخت به صورت استاتیک آنالیز کرد. مقادیر داینامیک مانند متغیرها نادیده گرفته میشوند.
پیکربندی تطبیق دهندهها:
- باید با
/شروع شوند - میتوانند شامل پارامترهای نامگذاری شده باشند:
/about/:pathمطابق با/about/aو/about/bاما نه/about/a/c - میتوانند روی پارامترهای نامگذاری شده تغییردهنده داشته باشند (با
:شروع میشود):/about/:path*مطابق با/about/a/b/cزیرا*صفر یا بیشتر است.?صفر یا یک است و+یک یا بیشتر - میتوانند از عبارت باقاعده محصور در پرانتز استفاده کنند:
/about/(.*)همان/about/:path*است
جزئیات بیشتر در مستندات path-to-regexp (opens in a new tab) را بخوانید.
خوب است بدانید: برای سازگاری با نسخههای قبلی، 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))
}
}NextResponse
NextResponse API به شما امکان میدهد:
- تغییر مسیر (
redirect) درخواست ورودی به یک URL دیگر - بازنویسی (
rewrite) پاسخ با نمایش یک URL مشخص - تنظیم سرصفحههای درخواست برای API Routes،
getServerSidePropsوrewriteمقصدها - تنظیم کوکیهای پاسخ
- تنظیم سرصفحههای headers پاسخ
برای تولید پاسخ از میان افزار میتوانید:
- به مسیری (صفحه یا هندلر مسیر) که یک پاسخ تولید میکند بازنویسی (rewrite) کنید
- مستقیماً یک
NextResponseبرگردانید. بخش تولید پاسخ را ببینید.
استفاده از کوکیها (Using Cookies)
کوکیها هدرهای معمولی هستند. در یک درخواست Request، آنها در هدر Cookie ذخیره میشوند. در یک پاسخ Response آنها در هدر Set-Cookie قرار دارند. Next.js یک روش راحت برای دسترسی و دستکاری این کوکیها از طریق افزونه کوکیها روی 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) {
// Assume a "Cookie:nextjs=fast" header to be present on the incoming request
// Getting cookies from the request using the `RequestCookies` API
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
// Setting cookies on the response using the `ResponseCookies` API
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: '/' }
// The outgoing response will have a `Set-Cookie:vercel=fast;path=/` header.
return response
}تنظیم هدرها
میتوانید با استفاده از NextResponse API هدرهای درخواست و پاسخ را تنظیم کنید (تنظیم هدرهای درخواست از Next.js نسخه ۱۳ به بعد در دسترس است).
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Clone the request headers and set a new header `x-hello-from-middleware1`
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-hello-from-middleware1', 'hello')
// You can also set request headers in NextResponse.rewrite
const response = NextResponse.next({
request: {
// New request headers
headers: requestHeaders,
},
})
// Set a new response header `x-hello-from-middleware2`
response.headers.set('x-hello-from-middleware2', 'hello')
return response
}خوب است بدانید: از تنظیم هدرهای بزرگ خودداری کنید زیرا ممکن است باعث خطای 431 Request Header Fields Too Large (opens in a new tab) بسته به پیکربندی سرور وب پشتیبان شما شود.
CORS (Cross-origin resource sharing)
میتوانید هدرهای CORS را در میان افزار برای اجازه دادن به درخواستهای متقابل (cross-origin) تنظیم کنید، از جمله درخواستهای ساده (opens in a new tab) و preflighted (opens in a new tab).
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*',
}خوب است بدانید: میتوانید هدرهای CORS را برای مسیرهای جداگانه در هندلرهای مسیر پیکربندی کنید.
تولید پاسخ Producing a Response
میتوانید مستقیماً با برگرداندن یک نمونه Response یا NextResponse از میان افزار پاسخ دهید. (این قابلیت از Next.js نسخه ۱۳.۱ (opens in a new tab) به بعد در دسترس است)
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 }
)
}
}waitUntil و NextFetchEvent
شیء NextFetchEvent شیء FetchEvent (opens in a new tab) بومی را گسترش میدهد و شامل متد waitUntil() (opens in a new tab) میشود.
متد waitUntil() یک promise به عنوان آرگومان میگیرد و طول عمر میان افزار را تا زمانی که promise حل شود، تمدید میکند. این برای انجام کار در پسزمینه مفید است.
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()
}پرچمهای پیشرفته میان افزار Flags
در v13.1 Next.js، دو پرچم اضافی برای میان افزار معرفی شد، skipMiddlewareUrlNormalize و skipTrailingSlashRedirect برای رسیدگی به موارد استفاده پیشرفته.
skipTrailingSlashRedirect بازنویسیهای Next.js را برای اضافه یا حذف اسلشهای انتهایی غیرفعال میکند. این به میان افزار اجازه میدهد تا به صورت سفارشی برای حفظ اسلش انتهایی در برخی مسیرها اما نه مسیرهای دیگر عمل کند که میتواند مهاجرتهای افزایشی را آسانتر کند.
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+)/)
) {
return NextResponse.redirect(
new URL(`${req.nextUrl.pathname}/`, 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
}اجرا Runtime
میان افزار در حال حاضر فقط از Edge runtime پشتیبانی میکند. محیط اجرای Node.js قابل استفاده نیست.
تاریخچه نسخه
| نسخه | تغییرات |
|---|---|
v13.1.0 | پرچم های میان افزار پیشرفته اضافه شد |
v13.0.0 | میانافزار میتواند سرصفحههای درخواست، سرصفحههای پاسخ و ارسال پاسخها را تغییر دهد |
v12.2.0 | میان افزار پایدار است، لطفاً راهنمای ارتقا را ببینید |
v12.0.9 | Enforce absolute URLs in Edge Runtime (PR (opens in a new tab)) |
v12.0.0 | میان افزار (بتا) اضافه شد |