Middleware runs before a request reaches your pages or API routes. Use it for authentication, redirects, geolocation, and header manipulation.

Creating Middleware

Create middleware.ts at the project root (same level as app/):

  // middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
    console.log('Path:', request.nextUrl.pathname);
    return NextResponse.next();
}

export const config = {
    matcher: '/dashboard/:path*',
};
  

The matcher config controls which routes trigger middleware. Without it, middleware runs on every request.

Redirects

  export function middleware(request: NextRequest) {
    if (request.nextUrl.pathname === '/old-page') {
        return NextResponse.redirect(new URL('/new-page', request.url));
    }
    return NextResponse.next();
}
  

Authentication Guard

Protect routes by checking for a session cookie or token:

  export function middleware(request: NextRequest) {
    const token = request.cookies.get('session-token');
    const isAuthPage = request.nextUrl.pathname.startsWith('/login');

    if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
        const loginUrl = new URL('/login', request.url);
        loginUrl.searchParams.set('callbackUrl', request.nextUrl.pathname);
        return NextResponse.redirect(loginUrl);
    }

    if (token && isAuthPage) {
        return NextResponse.redirect(new URL('/dashboard', request.url));
    }

    return NextResponse.next();
}

export const config = {
    matcher: ['/dashboard/:path*', '/login'],
};
  

Rewrites and Headers

Serve a different URL internally without changing the browser address:

  export function middleware(request: NextRequest) {
    if (request.nextUrl.pathname.startsWith('/api/v2')) {
        const url = request.nextUrl.clone();
        url.pathname = url.pathname.replace('/api/v2', '/api/v1');
        return NextResponse.rewrite(url);
    }
    return NextResponse.next();
}
  

Add security headers:

  export function middleware(request: NextRequest) {
    const response = NextResponse.next();
    response.headers.set('X-Frame-Options', 'DENY');
    response.headers.set('X-Content-Type-Options', 'nosniff');
    return response;
}
  

Matcher Patterns

  export const config = {
    matcher: [
        '/dashboard/:path*',
        '/admin/:path*',
        '/((?!_next/static|_next/image|favicon.ico|api).*)',
    ],
};
  
Pattern Matches
/dashboard/:path* /dashboard, /dashboard/settings
/api/:path* All API routes

Geolocation and A/B Testing

Access geo data on Vercel:

  export function middleware(request: NextRequest) {
    const country = request.geo?.country || 'US';
    if (country === 'DE') {
        return NextResponse.redirect(new URL('/de', request.url));
    }
    return NextResponse.next();
}
  

Limitations

  • Runs on the Edge Runtime — no Node.js APIs (fs, native database drivers)
  • Keep logic fast — middleware blocks every matched request
  • For heavy auth logic, verify tokens in middleware and load full session in Server Components

Next: style your app with CSS Modules and Tailwind CSS.