mediumFull Stack EngineerEnterprise
How does Next.js middleware work, and what are its limitations compared to regular API routes?
Posted 18/04/2026
by Mehedy Hasan Ador
Question Details
At an enterprise company interview:
> "We need to implement role-based access control. Should we use middleware or check auth in each API route? What are the trade-offs?"
> "We need to implement role-based access control. Should we use middleware or check auth in each API route? What are the trade-offs?"
Suggested Solution
Next.js Middleware
// middleware.ts (root of project)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('session-token');
// Protect dashboard routes
if (request.nextUrl.pathname.startsWith('/dashboard')) {
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
}
// Role-based check
if (request.nextUrl.pathname.startsWith('/admin')) {
const role = request.headers.get('x-user-role');
if (role !== 'admin') {
return NextResponse.redirect(new URL('/unauthorized', request.url));
}
}
// Add custom headers
const response = NextResponse.next();
response.headers.set('x-pathname', request.nextUrl.pathname);
return response;
}
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*', '/api/:path*'],
};
Where Middleware Runs
Middleware executes on the Edge Runtime, NOT Node.js:Limitations
fsBest Practice: Layered Auth
// Layer 1: Middleware — Fast checks (token exists? redirect?)
// middleware.ts
export function middleware(request: NextRequest) {
const token = request.cookies.get('session-token');
if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return NextResponse.next();
}
// Layer 2: API Route — Deep validation (token valid? user exists? role check?)
// app/api/admin/users/route.ts
export async function GET(req: Request) {
const session = await verifySession(req); // Full DB lookup
if (session.role !== 'admin') {
return Response.json({ error: 'Forbidden' }, { status: 403 });
}
// ... safe to proceed
}
// Layer 3: Component — UX (redirect if somehow reached)
// app/admin/page.tsx
import { redirect } from 'next/navigation';
export default async function AdminPage() {
const session = await getSession();
if (!session) redirect('/login');
// ...
}
Rule: Middleware for routing decisions, API routes for data validation.