Careeroza
Real World Example
Protecting an Admin Area
SaaS dashboards (Razorpay Dashboard, Zoho, Freshworks) redirect logged-out users to login, keep the target URL, and block non-admins from admin pages early. ShopEase middleware decides the outcome from the cookie and a role claim:
Example
The decision logic of the middleware as a pure function (easy to unit test):
type Session = { userId: string; role: 'customer' | 'admin' } | null;
type Decision = { action: 'next' } | { action: 'redirect'; to: string };
function decide(pathname: string, session: Session): Decision {
const needsLogin = ['/account', '/checkout', '/admin'].some((p) => pathname === p || pathname.startsWith(p + '/'));
if (needsLogin && !session) {
return { action: 'redirect', to: '/login?next=' + encodeURIComponent(pathname) };
}
if (pathname.startsWith('/admin') && session?.role !== 'admin') {
return { action: 'redirect', to: '/403' };
}
if (pathname === '/login' && session) return { action: 'redirect', to: '/account' };
return { action: 'next' };
}
const customer: Session = { userId: 'u1', role: 'customer' };
const admin: Session = { userId: 'u2', role: 'admin' };
const cases: [string, Session][] = [
['/products/42', null], ['/checkout', null], ['/admin/orders', customer],
['/admin/orders', admin], ['/login', customer], ['/account/orders', customer],
];
for (const [path, s] of cases) {
console.log(path.padEnd(16), (s?.role ?? 'guest').padEnd(9), JSON.stringify(decide(path, s)));
}Output
/products/42 guest {"action":"next"}
/checkout guest {"action":"redirect","to":"/login?next=%2Fcheckout"}
/admin/orders customer {"action":"redirect","to":"/403"}
/admin/orders admin {"action":"next"}
/login customer {"action":"redirect","to":"/account"}
/account/orders customer {"action":"next"}Wiring It Up
// middleware.ts (proxy.ts in Next.js 16)
import { NextRequest, NextResponse } from 'next/server';
import { decide } from '@/lib/access';
import { readSession } from '@/lib/session'; // verifies a signed cookie, no DB call
export async function middleware(req: NextRequest) {
const session = await readSession(req.cookies.get('session')?.value);
const d = decide(req.nextUrl.pathname, session);
return d.action === 'redirect' ? NextResponse.redirect(new URL(d.to, req.url)) : NextResponse.next();
}
export const config = { matcher: ['/account/:path*', '/checkout/:path*', '/admin/:path*', '/login'] };