NextjsMiddleware

Middleware

Middleware lets you run code before a request completes, intercepting it as it arrives and before it reaches a page, layout, or Route Handler. It's the one place in a Next.js app to apply logic — redirects, rewrites, header/cookie tweaks — uniformly across many routes without repeating that logic in every page.
One file, project-wide
Middleware lives in a single middleware.ts (or .js) file at the root of your project — next to app/, not inside it (or inside src/ if you're using that layout). You export a function named middleware, which Next.js runs for every matching request before routing decides which page or handler to invoke.

middleware.ts

TSX
import { NextRequest, NextResponse } from 'next/server'

export function middleware(request: NextRequest) {
  const isLoggedIn = request.cookies.has('session')

  if (!isLoggedIn && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  return NextResponse.next()
}
The three responses a middleware can return

Method

Effect

NextResponse.next()

Let the request continue to the matched page/route as normal (optionally with modified request headers)

NextResponse.redirect(url)

Send the browser an HTTP redirect to a different URL — the browser address bar changes

NextResponse.rewrite(url)

Serve content from a different internal path while keeping the URL the browser sees unchanged

Rewrites are especially useful for things like A/B testing or multi-tenant apps: the visitor stays on example.com/pricing, but you quietly serve them /pricing-variant-b based on a cookie.

middleware.ts — rewrite example

TSX
import { NextRequest, NextResponse } from 'next/server'

export function middleware(request: NextRequest) {
  const variant = request.cookies.get('ab-variant')?.value ?? 'a'

  if (request.nextUrl.pathname === '/pricing' && variant === 'b') {
    return NextResponse.rewrite(new URL('/pricing-variant-b', request.url))
  }

  return NextResponse.next()
}
Reading and setting cookies
NextRequest and NextResponse both expose a convenient .cookies API, so middleware can read incoming cookies and set new ones on the outgoing response without touching the raw Set-Cookie header format.

middleware.ts — setting a cookie

TSX
import { NextRequest, NextResponse } from 'next/server'

export function middleware(request: NextRequest) {
  const response = NextResponse.next()

  if (!request.cookies.has('visitor-id')) {
    response.cookies.set('visitor-id', crypto.randomUUID(), {
      httpOnly: true,
      maxAge: 60 * 60 * 24 * 365,
    })
  }

  return response
}
Middleware runs on the Edge Runtime
By default, middleware executes on the Edge Runtime, not Node.js. That means no direct filesystem access, and only a subset of Node APIs are available — many database drivers, ORMs, and Node-only packages simply won't work inside middleware.ts. Keep middleware limited to lightweight checks (cookies, headers, URL logic) and do heavier data access in the page or Route Handler it forwards to.
Note
Middleware runs for every matching request, including prefetches Next.js triggers for links in the viewport — so it needs to be fast. The next page covers the matcher config that scopes which paths middleware actually runs on.
  • Middleware runs before a request reaches a page, layout, or Route Handler, from a single middleware.ts at the project root.

  • It exports a middleware(request: NextRequest) function that returns NextResponse.next(), .redirect(), or .rewrite().

  • Rewrites change what's served without changing the URL the browser shows; redirects change the URL itself.

  • request.cookies / response.cookies provide a friendly API for reading and setting cookies.

  • Middleware runs on the Edge Runtime by default, so it should stay lightweight and avoid Node-only APIs.