middleware
middleware.ts: הפניות, שכתובים, כותרות וקובצי Cookie לפני כל בקשה, עם config.matcher.
middleware מריץ קוד לפני השלמת הבקשה. הוא יכול להפנות, לשכתב, להגדיר כותרות וקובצי Cookie, או להחזיר תגובה בעצמו.
מוסכמות
יש ליצור את middleware.ts בשורש הפרויקט (או בספרייה src/), לצד app/. יש לייצא פונקציית middleware (או ייצוא ברירת מחדל) ובאופן אופציונלי גם config.
import { NextResponse, type NextRequest } from "@textjs/app/server";export function middleware(request: NextRequest) { if (!request.cookies.has("session")) { return NextResponse.redirect(new URL("/login", request.url)); } return NextResponse.next();}export const config = { matcher: ["/dashboard/:path*", "/api/:path*"],};middleware רץ לפני מטפלי נתיבים, קבצים סטטיים ודפי מסוף, בעת הפעלת text dev, text start וכל מתאם פריסה לשרת. הוא משתמש ב-Request וב-Response של Web, כך שאותו קובץ רץ ב-Node, ב-Bun, ב-Vercel Edge וב-Cloudflare Workers.
התאמת נתיבים
ללא config.matcher, middleware רץ עבור כל בקשה. כללי ההתאמה משתמשים באותו תחביר כמו Next.js:
| כלל התאמה | התאמות |
|---|---|
/about | /about וגם /about/ |
/blog/:slug | /blog/hello |
/docs/:path* | /docs, /docs/a/b |
/a/:rest+ | /a/b, לא /a |
/user/:id(\\d+) | /user/42, לא /user/ada |
/((?!api/).*) | הכול מלבד /api/* |
יש להוסיף תנאים באמצעות has ו-missing (כותרת, קובץ Cookie, שאילתה או מארח):
export const config = { matcher: [{ source: "/beta/:path*", has: [{ type: "cookie", key: "beta", value: "1" }] }],};כללי ההתאמה חלים על שם הנתיב ללא basePath. ב-text dev, בקשות המודולים של Vite עצמו (/@vite/*, קובצי מקור) לעולם אינן מפעילות middleware.
כל מארח מחשב שם נתיב קנוני אחד לפני הפעלת middleware, ומשתמש בו להתאמה, ל-request.nextUrl ולחיפוש הנתיב, המטפל והקובץ. רצפי קידוד באחוזים מפוענחים והנתיב מקודד מחדש באופן שבו new URL() כותב אותו, מקטעי . ו-.. נפתרים, ולוכסנים חוזרים וסופיים מוסרים. /ad%6din//users/ מגיע ל-middleware בתור /admin/users, ואילו /café נשאר /caf%C3%A9. כלל התאמה שנכתב עם לוכסן סופי, כגון /admin/, מתאים ל-/admin. בקשות שהנתיב שלהן מכיל לוכסן או לוכסן הפוך מקודד (%2F, %5C), תו בקרה או קידוד אחוזים לא תקין מקבלות 400. נתיבים רגישים לרישיות: /Admin אינו מתאים ל-/admin, וקבצים סטטיים מוגשים רק בשם המדויק שלהם.
יש להתאים את כתובות ה-URL שבהן הקובץ מוגש, ולא רק את שם הקובץ שלו. Cloudflare Pages מגיש את report.html גם ב-/report, ולכן כלל התאמה עבור /report.html אינו מגן על /report. יש לציין את שניהם, matcher: ["/report", "/report.html"], או לשמור קבצים מוגנים בספרייה ולהתאים אותה, למשל /reports/:path* עבור קבצים בספרייה public/reports/.
NextResponse
| API | השפעה |
|---|---|
NextResponse.next() | המשך לנתיב |
NextResponse.redirect(url, status?) | 307 כברירת מחדל; url חייב להיות מוחלט |
NextResponse.rewrite(url) | הגשת נתיב אחר; מקורות חיצוניים מועברים דרך שרת מתווך |
NextResponse.json(body, init?) | החזרת תגובה עם JSON |
new Response(...) | החזרת תגובה עם כל תוכן |
כדי להמשיך ללא שינוי, אין להחזיר דבר. next() ו-rewrite() מקבלות { status } לשינוי קוד המצב של התגובה הסופית. רק תגובות שנוצרו באמצעות NextResponse.next() / NextResponse.rewrite() מכוונות את הניתוב: כל Response אחר, כולל כזה שהוחזר מ-fetch(), נשלח ללקוח כפי שהוא, לאחר הסרת כותרות ה-x-middleware-* שלו.
request.nextUrl אינו כולל את basePath בתוך pathname, ואילו href ו-toString() שלו כוללים אותו. כדי להישאר בתוך האפליקציה, יש לשכפל אותו: const url = request.nextUrl.clone(); url.pathname = "/login"; return NextResponse.redirect(url);.
export function middleware(request: NextRequest) { if (request.nextUrl.pathname === "/status") { return NextResponse.rewrite(new URL("/api/health", request.url)); }}כותרות
יש להעביר את request.headers כדי לשנות את מה שהמטפלים בהמשך מקבלים, ואת headers כדי להוסיף כותרות תגובה:
export function middleware(request: NextRequest) { const headers = new Headers(request.headers); headers.set("x-user", "ada"); const response = NextResponse.next({ request: { headers } }); response.headers.set("x-frame", "▼"); return response;}כותרות תגובה מ-middleware מתווספות לתגובה הסופית, אלא אם מטפל הנתיב מגדיר את אותה כותרת.
קובצי Cookie
request.cookies קורא את הכותרת Cookie; response.cookies כותב את Set-Cookie.
export function middleware(request: NextRequest) { const theme = request.cookies.get("theme")?.value ?? "geistDark"; const response = NextResponse.next(); response.cookies.set("theme", theme, { httpOnly: true, sameSite: "lax", maxAge: 60 * 60 * 24 }); return response;}קובצי Cookie שמוגדרים בתגובת next() או rewrite() זמינים גם למטפל הנתיב באותה בקשה.
עבודה ברקע
הארגומנט השני כולל את waitUntil(), שמשאיר סביבות ריצה ללא שרת פעילות לאחר שליחת התגובה:
import type { NextFetchEvent, NextRequest } from "@textjs/app/server";export function middleware(request: NextRequest, event: NextFetchEvent) { event.waitUntil(fetch("https://analytics.example.com", { method: "POST", body: request.url }));}הערות
- דפים עם רינדור בצד הלקוח מרונדרים בדפדפן, ולכן שכתוב לדף אחר משנה את הקובץ המוגש, ולא את הנתיב שאפליקציית המסוף מרנדרת. שכתובים למטפלי נתיבים, לקבצים סטטיים ולמקורות חיצוניים פועלים כמצופה.
- middleware יכול לקרוא את גוף הבקשה; מטפלי נתיבים עדיין מקבלים אותו. גוף הבקשות שכלל התאמה מחריג נשאר ללא שינוי.
request.urlמכיל תמיד את מקור השרת: יעדי בקשה כגון//other.host/xמנורמלים ללוכסן מוביל יחיד.- המתאם הסטטי אינו יכול להריץ middleware.