ข้ามไปยังเนื้อหา

Middleware และ Request Lifecycle

ไฟล์ middleware.ts ที่ root ของโปรเจกต์จะรันก่อน request จะเสร็จ บนทุก route ที่ match ทำให้เรา redirect, rewrite หรือ set header กับ cookie ได้ก่อนที่ Next.js จะ render อะไรออกมา

middleware ดัก request หลังจาก request มาถึงแต่ก่อน route จะถูก resolve เรา return NextResponse เพื่อ redirect, rewrite หรือปล่อยผ่านด้วย NextResponse.next()

// middleware.ts — runs BEFORE the request completes, on every matching route
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
// Lightweight auth gate: redirect logged-out users away from /dashboard
const isLoggedIn = request.cookies.has('session')
if (request.nextUrl.pathname.startsWith('/dashboard') && !isLoggedIn) {
return NextResponse.redirect(new URL('/login', request.url))
}
// Otherwise continue, and set a header on the way through
const response = NextResponse.next()
response.headers.set('x-request-time', Date.now().toString())
return response
}

ถ้าไม่มี matcher middleware จะรันทุก request รวมถึง static asset ด้วย เราจึงควร scope ให้รันเฉพาะจุดที่ต้องการ config matcher รับ path pattern และ exclude ส่วน internal ได้

// middleware.ts — scope which routes run middleware
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}

ใช้ middleware สำหรับ redirect, rewrite (URL เดิมแต่เสิร์ฟ content ต่างกัน) และ set header หรือ cookie การ rewrite เสิร์ฟ content คนละแบบโดยไม่เปลี่ยน URL บน browser เหมาะกับ A/B test หรือ geo routing

// A/B or geo rewrite — same URL, different content served
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const country = request.headers.get('x-vercel-ip-country') ?? 'us'
return NextResponse.rewrite(new URL(`/${country}${request.nextUrl.pathname}`, request.url))
}

middleware รันบน Edge runtime เป็น default และอยู่หน้าทุก request ที่ match เพราะฉะนั้นต้องเร็ว ไม่ยิง database ไม่คำนวณหนัก ไม่ใช้ dependency ใหญ่ ตัดสินใจเบา ๆ ตรงนี้แล้วโยนงานจริงไปที่ route

graph LR
  A["Incoming request"] --> B["middleware.ts (Edge)"]
  B -->|redirect| C["NextResponse.redirect"]
  B -->|rewrite| D["NextResponse.rewrite"]
  B -->|next| E["Route handler / page"]
Middleware intercepts before the route resolves
When does middleware.ts run?
What is the matcher config used for?
What runtime does middleware use by default?
Which of these belongs in middleware?