Skip to content
Watch the complete Next.js 15 course on YouTube

Middleware

This page explains how to implement and use middleware in Next.js.

Understanding middleware

Middleware in Next.js is a powerful feature that lets you intercept and control the flow of requests and responses throughout your application. It runs before a request is completed, allowing you to modify the response based on the incoming request.

Middleware can be used for:

  • Redirecting users
  • Rewriting URLs
  • Modifying request or response headers
  • Setting or reading cookies
  • Authentication and authorization
  • A/B testing

Creating middleware

To create middleware, add a middleware.js or middleware.ts file in your project’s root or src directory:

middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// Your middleware logic here
return NextResponse.next();
}

Specifying paths for middleware

You can specify which paths the middleware should run on using two approaches:

1. Using the matcher config

middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL("/", request.url));
}
export const config = {
matcher: "/profile",
};

This middleware will only run when the /profile path is accessed.

2. Using conditional statements

middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname === "/profile") {
return NextResponse.redirect(new URL("/", request.nextUrl));
}
}

This approach gives you more flexibility to apply different logic based on the request path.

Redirecting users

You can redirect users from one URL to another:

middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// Redirect /profile to the home page
if (request.nextUrl.pathname === "/profile") {
return NextResponse.redirect(new URL("/", request.nextUrl));
}
}

URL rewrites

Rewrites allow you to serve content from a different URL while keeping the original URL in the browser:

middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// Rewrite /profile to /hello
if (request.nextUrl.pathname === "/profile") {
return NextResponse.rewrite(new URL("/hello", request.nextUrl));
}
}

With this middleware, when a user visits /profile, they’ll see the content from /hello, but the URL in the browser will remain /profile.

Working with cookies

You can read and set cookies in middleware:

middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// Get the response that would normally be returned
const response = NextResponse.next();
// Check if the user has a theme preference
const themePreference = request.cookies.get("theme");
// If no theme preference is set, default to dark
if (!themePreference) {
response.cookies.set("theme", "dark");
}
return response;
}

Modifying headers

You can add or modify headers in the response:

middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// Get the response
const response = NextResponse.next();
// Set a custom header
response.headers.set("custom-header", "custom-value");
return response;
}

Combining multiple middleware features

You can combine multiple middleware features in a single implementation:

middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// For the profile page, redirect to login if not authenticated
if (request.nextUrl.pathname === "/profile") {
const authCookie = request.cookies.get("authenticated");
if (!authCookie || authCookie.value !== "true") {
return NextResponse.redirect(new URL("/login", request.nextUrl));
}
}
// For all requests, set a custom header
const response = NextResponse.next();
response.headers.set("x-app-version", "1.0.0");
return response;
}
export const config = {
matcher: ["/profile", "/dashboard/:path*"],
};

Good to know

  • Middleware runs before cached content is served
  • The matcher config supports path matching, negative lookaheads, and regular expressions
  • You can only have one middleware file in your project
  • Middleware doesn’t run for static assets in the public directory
  • For complex path matching, use the matcher config instead of conditional statements