M

Middleware

Type-safe Next.js middleware for authentication, redirects, and request modification.

Code

typescript
1// middleware.ts (in project root)
2import { NextResponse } from "next/server";
3import type { NextRequest } from "next/server";
4
5// Types for JWT payload
6interface JWTPayload {
7 sub: string;
8 email: string;
9 role: "admin" | "user" | "guest";
10 exp: number;
11}
12
13// Protected routes configuration
14const protectedRoutes = ["/dashboard", "/profile", "/settings"];
15const adminRoutes = ["/admin"];
16const authRoutes = ["/login", "/register"];
17
18// Simple JWT decoder (use a library in production)
19function decodeToken(token: string): JWTPayload | null {
20 try {
21 const payload = token.split(".")[1];
22 return JSON.parse(atob(payload));
23 } catch {
24 return null;
25 }
26}
27
28export function middleware(request: NextRequest) {
29 const { pathname } = request.nextUrl;
30 const token = request.cookies.get("auth-token")?.value;
31
32 // Decode and validate token
33 const user = token ? decodeToken(token) : null;
34 const isAuthenticated = user && user.exp * 1000 > Date.now();
35
36 // Check if route is protected
37 const isProtectedRoute = protectedRoutes.some((route) =>
38 pathname.startsWith(route)
39 );
40 const isAdminRoute = adminRoutes.some((route) => pathname.startsWith(route));
41 const isAuthRoute = authRoutes.some((route) => pathname.startsWith(route));
42
43 // Redirect authenticated users away from auth pages
44 if (isAuthRoute && isAuthenticated) {
45 return NextResponse.redirect(new URL("/dashboard", request.url));
46 }
47
48 // Redirect unauthenticated users to login
49 if (isProtectedRoute && !isAuthenticated) {
50 const loginUrl = new URL("/login", request.url);
51 loginUrl.searchParams.set("callbackUrl", pathname);
52 return NextResponse.redirect(loginUrl);
53 }
54
55 // Check admin access
56 if (isAdminRoute) {
57 if (!isAuthenticated) {
58 return NextResponse.redirect(new URL("/login", request.url));
59 }
60 if (user?.role !== "admin") {
61 return NextResponse.redirect(new URL("/unauthorized", request.url));
62 }
63 }
64
65 // Add user info to headers for server components
66 const response = NextResponse.next();
67 if (user) {
68 response.headers.set("x-user-id", user.sub);
69 response.headers.set("x-user-role", user.role);
70 }
71
72 return response;
73}
74
75// Configure which routes middleware runs on
76export const config = {
77 matcher: [
78 /*
79 * Match all request paths except for:
80 * - api (API routes)
81 * - _next/static (static files)
82 * - _next/image (image optimization)
83 * - favicon.ico (favicon file)
84 */
85 "/((?!api|_next/static|_next/image|favicon.ico).*)",
86 ],
87};
88
89// middleware/withAuth.ts - Middleware composition helper
90type MiddlewareHandler = (
91 request: NextRequest
92) => NextResponse | Promise<NextResponse>;
93
94export function composeMiddleware(...handlers: MiddlewareHandler[]) {
95 return async (request: NextRequest): Promise<NextResponse> => {
96 for (const handler of handlers) {
97 const response = await handler(request);
98 if (response.status !== 200) {
99 return response;
100 }
101 }
102 return NextResponse.next();
103 };
104}

Run this example locally

$ npx create-next-app@latest --typescript