Topic 7 of 47
Middleware & Authentication
Overview
Next.js Middleware runs on the Edge before a request is completed — perfect for authentication, authorization, redirects, A/B testing, and geolocation. It intercepts every request without hitting your server.
Syntax
typescript
// middleware.ts (in project root)
import { NextRequest, NextResponse } from 'next/server';
import { verifyJWT } from '@/lib/auth';
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Protect /dashboard and /api routes
if (pathname.startsWith('/dashboard') || pathname.startsWith('/api/protected')) {
const token = request.cookies.get('token')?.value
?? request.headers.get('Authorization')?.replace('Bearer ', '');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
try {
const payload = await verifyJWT(token);
// Pass user info to route via headers
const response = NextResponse.next();
response.headers.set('x-user-id', payload.sub);
return response;
} catch {
return NextResponse.redirect(new URL('/login', request.url));
}
}
return NextResponse.next();
}
// Specify which routes the middleware applies to
export const config = {
matcher: ['/dashboard/:path*', '/api/protected/:path*'],
};Common Pitfalls
- Middleware runs on the Edge Runtime — you can't use Node.js-specific APIs (fs, crypto module, etc.). Use Web APIs instead.
- Middleware runs on EVERY request matching the matcher — keep it fast. Heavy operations kill performance.
- Interview tip: Middleware vs Route Handler — Middleware runs before routing and can't return data. Use it for auth/redirects. Use Route Handlers for API responses.
Real-World Example
Next.js Middleware for multi-tenant routing and locale detection:
example
typescript
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
const SUPPORTED_LOCALES = ['en', 'hi', 'mr', 'ta'];
const DEFAULT_LOCALE = 'en';
export function middleware(request: NextRequest) {
const { pathname, hostname } = request.nextUrl;
// Multi-tenant: subdomain routing
const subdomain = hostname.split('.')[0];
if (['app', 'admin', 'api'].includes(subdomain)) {
// Route to different layouts based on subdomain
const newUrl = request.nextUrl.clone();
newUrl.pathname = `/${subdomain}${pathname}`;
return NextResponse.rewrite(newUrl);
}
// Locale detection
const locale = request.cookies.get('locale')?.value
?? request.headers.get('accept-language')?.split(',')[0].slice(0,2)
?? DEFAULT_LOCALE;
const validLocale = SUPPORTED_LOCALES.includes(locale) ? locale : DEFAULT_LOCALE;
// Redirect to locale-prefixed URL
if (!SUPPORTED_LOCALES.some(loc => pathname.startsWith(`/${loc}`))) {
return NextResponse.redirect(new URL(`/${validLocale}${pathname}`, request.url));
}
return NextResponse.next();
}