Next.js Middleware Authentication
Implement authentication at the edge with Next.js Middleware for protected routes.
Overview
Next.js Middleware runs before requests complete, enabling authentication and authorization checks at the edge without hitting your application code. This approach reduces latency by rejecting unauthorized requests early and offloads common security checks from your application servers. Middleware receives a NextRequest object and can inspect cookies, headers, and other request properties. For JWT-based authentication, the token can be verified using libraries like jose that work in edge runtimes. If validation fails, NextResponse.redirect() immediately sends the user to a login page without ever invoking page components or API routes. Protected routes are configured using the matcher option in middleware config. This allows precise control over which paths require authentication while avoiding middleware overhead on static assets and public routes. Multiple matchers can be combined with negative patterns for exclusions. The middleware approach has trade-offs compared to higher-level solutions. It cannot access database directly (relying on JWT or stateless tokens), and it executes on every matching request before cache. For complex authorization rules requiring database lookups, middleware should verify basic auth and let page-level checks handle fine-grained permissions. Edge runtime constraints affect available APIs. Node.js-specific features like Buffer or crypto require using web-compatible equivalents. Database drivers often don't work in edge runtime; if you need database authorization checks, consider using a lightweight API call or accepting that full auth happens in application code after middleware passes.
Code Example
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { jwtVerify } from 'jose';
const JWT_SECRET = new TextEncoder().encode(
process.env.JWT_SECRET || 'development-secret-key'
);
const publicPaths = ['/login', '/register', '/api/auth', '/public'];
export async function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;
// Allow public paths without authentication
if (publicPaths.some(path => pathname.startsWith(path))) {
return NextResponse.next();
}
// Skip middleware for static files and images
if (
pathname.startsWith('/_next') ||
pathname.startsWith('/favicon') ||
pathname.includes('.')
) {
return NextResponse.next();
}
// Extract token from cookie or Authorization header
const token = request.cookies.get('auth_token')?.value ||
request.headers.get('Authorization')?.replace('Bearer ', '');
if (!token) {
const loginUrl = new URL('/login', request.url);
loginUrl.searchParams.set('redirect', pathname);
return NextResponse.redirect(loginUrl);
}
try {
const { payload } = await jwtVerify(token, JWT_SECRET);
// Add user info to headers for downstream use
const requestHeaders = new Headers(request.headers);
requestHeaders.set('x-user-id', payload.sub as string);
requestHeaders.set('x-user-role', payload.role as string);
// Check admin routes
if (pathname.startsWith('/admin') && payload.role !== 'admin') {
return NextResponse.redirect(new URL('/unauthorized', request.url));
}
return NextResponse.next({
request: {
headers: requestHeaders,
},
});
} catch (error) {
// Invalid or expired token
const response = NextResponse.redirect(new URL('/login', request.url));
response.cookies.delete('auth_token');
return response;
}
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|public).*)',
],
};More Next.js Rules
Next.js App Router Server Components First
Prioritize React Server Components for data fetching and static content. Client components only when interactivity is required.
'use server';
import { db } from '@/lib/database';
import { cache } from 'react';
export const getProjects = cache(async () => {
return db.project...Next.js Image Optimization Best Practices
Use the Image component with proper sizing, formats, and loading strategies for maximum performance.
import Image from 'next/image';
import Link from 'next/link';
export function ProjectCard({ project }: { project: Project }) {
return (
<Link h...Next.js Dynamic Metadata API
Use the Metadata API for SEO optimization with dynamic titles, descriptions, and OpenGraph images.
import { Metadata, ResolvingMetadata } from 'next';
import { notFound } from 'next/navigation';
import { db } from '@/lib/db';
import { SchemaMarkup }...