# Next.js 16 Middleware in 2026: Edge Runtime, Authentication and Interview Questions > Master Next.js 16 Middleware with Edge Runtime patterns, authentication strategies, and common interview questions. Learn request interception, path matching, and production-ready auth flows. - Published: 2026-09-20 - Updated: 2026-09-20 - Author: Anthony Fillion-Maillet - Reading time: 5 min --- Next.js 16 Middleware runs before every request hits the server, making it the first line of defense for authentication, localization, and request manipulation. Understanding middleware is essential for building production applications and a frequent topic in frontend interviews. > **Middleware runs at the Edge** > > Next.js 16 Middleware executes in the Edge Runtime by default, meaning it runs in data centers close to users with sub-millisecond cold starts. This makes it ideal for authentication checks and redirects that need to happen before the page renders. ## How Next.js 16 Middleware Works Under the Hood Middleware in Next.js 16 intercepts requests before they reach the route handler or page component. The middleware file must be placed at the root of the project (next to `app/` or `pages/`) and exports a default function that receives a `NextRequest` object. The Edge Runtime constraint means middleware cannot use Node.js APIs like `fs` or `Buffer`. It runs on V8 isolates, similar to Cloudflare Workers, which enables global distribution but limits available APIs to Web Standards. ```typescript // middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { // Access request headers, cookies, and URL const token = request.cookies.get('session')?.value const pathname = request.nextUrl.pathname // Log for debugging (visible in Vercel logs) console.log(`Middleware: ${request.method} ${pathname}`) // Continue to the route handler return NextResponse.next() } // Configure which paths trigger middleware export const config = { matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'], } ``` The `matcher` config uses a regex-like syntax to define which paths trigger the middleware. Without it, middleware runs on every request, including static assets. ## Authentication Patterns with Next.js Middleware Middleware excels at authentication because it runs before any page code executes. A user without a valid session never sees protected content, not even briefly. This differs from client-side auth checks that can flash protected content before redirecting. The following pattern validates a session token and redirects unauthenticated users. It integrates with any auth provider that stores session data in cookies or headers. ```typescript // middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' const protectedPaths = ['/dashboard', '/settings', '/profile'] const authPaths = ['/login', '/signup'] export async function middleware(request: NextRequest) { const { pathname } = request.nextUrl const sessionToken = request.cookies.get('session')?.value // Check if path requires authentication const isProtectedPath = protectedPaths.some(path => pathname.startsWith(path) ) const isAuthPath = authPaths.some(path => pathname.startsWith(path) ) // Validate session token (call auth service) const isValidSession = sessionToken ? await validateSession(sessionToken) : false // Redirect unauthenticated users to login if (isProtectedPath && !isValidSession) { const loginUrl = new URL('/login', request.url) loginUrl.searchParams.set('callbackUrl', pathname) return NextResponse.redirect(loginUrl) } // Redirect authenticated users away from auth pages if (isAuthPath && isValidSession) { return NextResponse.redirect(new URL('/dashboard', request.url)) } return NextResponse.next() } async function validateSession(token: string): Promise { // Call auth API endpoint or decode JWT // Edge-compatible: use fetch, not Node.js crypto try { const response = await fetch('https://api.example.com/validate', { headers: { Authorization: `Bearer ${token}` }, }) return response.ok } catch { return false } } ``` The `callbackUrl` parameter preserves the original destination, so users land on the page they initially requested after logging in. ## Path Matching and the Matcher Config The `matcher` configuration determines which requests trigger middleware. Getting this wrong leads to performance issues (middleware running on every static asset) or security holes (protected paths not triggering auth checks). Next.js 16 supports three matcher syntaxes: string paths, path patterns with parameters, and regex-like patterns with negative lookaheads. ```typescript // middleware.ts // Option 1: Simple string paths export const config = { matcher: ['/dashboard/:path*', '/api/:path*'], } // Option 2: Exclude static assets with negative lookahead export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico|public/).*)'], } // Option 3: Conditional matching with has/missing export const config = { matcher: [ { source: '/api/:path*', has: [{ type: 'header', key: 'Authorization' }], }, { source: '/dashboard/:path*', missing: [{ type: 'cookie', key: 'session' }], }, ], } ``` The `has` and `missing` conditions in option 3 add logic before middleware even runs. This is more efficient than checking inside the middleware function. ## Request and Response Manipulation in Middleware Beyond authentication, middleware can modify requests before they reach route handlers and responses before they reach the client. Common use cases include adding security headers, rewriting URLs for A/B testing, and injecting geolocation data. The `NextResponse` class provides methods for each modification type. Rewrites change the destination without changing the browser URL, while redirects update both. ```typescript // middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { const response = NextResponse.next() // Add security headers to all responses response.headers.set('X-Frame-Options', 'DENY') response.headers.set('X-Content-Type-Options', 'nosniff') response.headers.set( 'Referrer-Policy', 'strict-origin-when-cross-origin' ) // Inject geolocation into request headers for route handlers const geo = request.geo if (geo?.country) { response.headers.set('x-user-country', geo.country) response.headers.set('x-user-city', geo.city ?? 'unknown') } return response } ``` Geolocation data (`request.geo`) is populated by Vercel's Edge Network. Self-hosted deployments need to configure this through the hosting provider or use a third-party IP geolocation service. ## URL Rewriting for A/B Testing and Feature Flags Middleware rewrites let you serve different pages based on cookies, headers, or random assignment without the client knowing. This pattern powers A/B testing frameworks and gradual feature rollouts. The rewrite happens at the edge, so both variants have the same URL and benefit from edge caching when appropriate. ```typescript // middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { const { pathname } = request.nextUrl // Check for existing variant assignment let variant = request.cookies.get('ab-variant')?.value // Assign variant if not set (50/50 split) if (!variant && pathname === '/pricing') { variant = Math.random() < 0.5 ? 'control' : 'treatment' const response = NextResponse.rewrite( new URL(`/pricing/${variant}`, request.url) ) response.cookies.set('ab-variant', variant, { maxAge: 60 * 60 * 24 * 30, // 30 days httpOnly: true, }) return response } // Rewrite to assigned variant if (variant && pathname === '/pricing') { return NextResponse.rewrite( new URL(`/pricing/${variant}`, request.url) ) } return NextResponse.next() } ``` The cookie persists the variant assignment, ensuring users see the same version on return visits. The 30-day expiry balances experiment consistency with the ability to reassign users to new experiments. ## Edge Runtime Limitations and Workarounds The Edge Runtime trades Node.js compatibility for global distribution and fast cold starts. Middleware cannot import Node.js-specific modules, which affects libraries like `bcrypt`, `jsonwebtoken` (when using RS256), and database clients that rely on TCP sockets. [The Next.js Edge Runtime documentation](https://nextjs.org/docs/app/api-reference/edge) lists supported APIs. Web Crypto, fetch, and most Web Platform APIs work. Heavy computation should move to API routes running in Node.js. ```typescript // middleware.ts - JWT validation without jsonwebtoken import { jwtVerify } from 'jose' // Edge-compatible JWT library import type { NextRequest } from 'next/server' const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET!) export async function middleware(request: NextRequest) { const token = request.cookies.get('token')?.value if (!token) { return redirectToLogin(request) } try { // jose library works in Edge Runtime const { payload } = await jwtVerify(token, JWT_SECRET) // Token is valid, check expiration if (payload.exp && payload.exp < Date.now() / 1000) { return redirectToLogin(request) } return NextResponse.next() } catch { // Invalid token signature or format return redirectToLogin(request) } } function redirectToLogin(request: NextRequest) { const url = new URL('/login', request.url) url.searchParams.set('callbackUrl', request.nextUrl.pathname) return NextResponse.redirect(url) } ``` The [jose](https://github.com/panva/jose) library provides Edge-compatible JWT operations. It uses Web Crypto under the hood, avoiding the Node.js `crypto` module. ## Common Next.js Middleware Interview Questions Interviewers ask about middleware to assess understanding of request lifecycle, security patterns, and Edge computing constraints. These questions appear in senior frontend and fullstack roles working with Next.js. **Where does middleware execute in the request lifecycle?** Middleware runs after the request hits the Next.js server but before any route matching or page rendering. On Vercel, it executes at the Edge, meaning the closest data center to the user. This placement makes it ideal for authentication and redirects since invalid requests never reach the origin server. **What can middleware not do compared to API routes?** Middleware runs in the Edge Runtime, not Node.js. It cannot use Node.js built-in modules (`fs`, `path`, `crypto` with certain algorithms), TCP-based database clients, or npm packages that depend on Node.js APIs. Heavy computation should happen in API routes or Server Components. **How do you handle authentication in middleware without blocking?** The pattern involves checking for a session cookie, validating it against an auth endpoint or by verifying a JWT signature, and redirecting if invalid. To avoid blocking every request, use the `matcher` config to only run middleware on protected paths. Stateless JWT validation is faster than calling an external auth service on every request. **What happens if middleware throws an error?** An unhandled error in middleware returns a 500 response to the client. The page never renders. Wrapping middleware logic in try-catch and returning a fallback response (often `NextResponse.next()`) prevents complete request failures. Logging the error before continuing helps with debugging. For deeper practice with these concepts, the [Next.js Middleware and Auth interview questions](/technologies/react-next/interview-questions/nextjs-middleware-auth) module covers additional scenarios. ## Debugging Middleware in Development and Production Middleware bugs are tricky because the code runs at the edge, not in the browser DevTools. Development mode shows middleware logs in the terminal, but production requires proper logging infrastructure. The following approach logs middleware execution with enough context to debug issues without exposing sensitive data. ```typescript // middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { const start = Date.now() const requestId = crypto.randomUUID() // Log request start console.log(JSON.stringify({ type: 'middleware_request', requestId, method: request.method, path: request.nextUrl.pathname, userAgent: request.headers.get('user-agent')?.slice(0, 100), country: request.geo?.country, })) // Process request const response = processRequest(request) // Log completion console.log(JSON.stringify({ type: 'middleware_response', requestId, duration: Date.now() - start, status: response.status, })) // Pass request ID to route handlers response.headers.set('x-request-id', requestId) return response } function processRequest(request: NextRequest): NextResponse { // Middleware logic here return NextResponse.next() } ``` On Vercel, these logs appear in the Functions tab. Self-hosted deployments should send logs to a service like Datadog or use structured logging that the hosting platform can ingest. ## Middleware Performance Best Practices Middleware runs on every matched request. A slow middleware function adds latency to every page load. The goal is to complete middleware execution in under 5ms for most requests. - **Minimize async operations**: Each `await` adds latency. Cache validation results when possible. - **Use matcher config**: Exclude static assets and public paths that do not need middleware logic. - **Avoid external calls on every request**: Validate JWTs locally instead of calling an auth service. Cache results in cookies when appropriate. - **Keep the bundle small**: Middleware has its own bundle size limit (1MB on Vercel). Import only what is needed. The React Next.js technology page at [/technologies/react-next](/technologies/react-next) covers related concepts like Server Components and data fetching patterns that complement middleware knowledge. ## Next.js 16 Middleware Checklist for Production - Place middleware at the project root as `middleware.ts`, not inside `app/` or `pages/` - Configure the `matcher` to exclude `_next/static`, `_next/image`, and public assets from middleware execution - Use Edge-compatible libraries for JWT validation, such as `jose` instead of `jsonwebtoken` - Store callback URLs in query parameters when redirecting to login, so users return to their intended destination - Add security headers in middleware for consistent application across all routes - Log middleware execution with request IDs to trace requests through the system - Keep external API calls to a minimum by validating tokens locally and caching user data in signed cookies --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/react-next/nextjs-16-middleware-edge-runtime-authentication-interview-questions