Skip to main content
Practice Problems

middleware in Next.js

Middleware in Next.js is a function that runs before every request is processed. It lets you intercept requests and modify the response before it reaches the user.

How to Create Middleware

Middleware is defined in a middleware.ts file at the project root (next to app/ or pages/):

tsx
// middleware.ts import { NextResponse } from 'next/server' import type { NextRequest } from 'next/server' export function middleware(request: NextRequest) { const { pathname } = request.nextUrl if (pathname.startsWith('/admin')) { const token = request.cookies.get('session') if (!token) { return NextResponse.redirect(new URL('/auth/login', request.url)) } } return NextResponse.next() } export const config = { matcher: ['/admin/:path*', '/settings/:path*'] }

Matcher

config.matcher defines which paths trigger the middleware:

tsx
export const config = { matcher: [ '/admin/:path*', '/settings/:path*', '/((?!api|_next/static|_next/image|favicon.ico).*)' ] }

Without a matcher, middleware runs for every request including static files. That is unnecessary overhead.

Common Scenarios

Authorization

tsx
export function middleware(request: NextRequest) { const token = request.cookies.get('session') const isAuthPage = request.nextUrl.pathname.startsWith('/auth') if (!token && !isAuthPage) { return NextResponse.redirect(new URL('/auth/login', request.url)) } if (token && isAuthPage) { return NextResponse.redirect(new URL('/dashboard', request.url)) } return NextResponse.next() }

Internationalization (i18n)

This is how localization works on IT Lead: middleware detects the user's language and redirects to the corresponding path.

tsx
const locales = ['ru', 'en'] const defaultLocale = 'ru' export function middleware(request: NextRequest) { const { pathname } = request.nextUrl const hasLocale = locales.some( locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}` ) if (hasLocale) return NextResponse.next() const locale = request.headers.get('accept-language')?.startsWith('en') ? 'en' : defaultLocale return NextResponse.redirect( new URL(`/${locale}${pathname}`, request.url) ) }

Adding Headers

tsx
export function middleware(request: NextRequest) { const response = NextResponse.next() response.headers.set('x-request-id', crypto.randomUUID()) response.headers.set( 'Content-Security-Policy', "default-src 'self'" ) return response }

Edge Runtime

Middleware runs on Edge Runtime, not Node.js. This means:

  • Fast startup (no cold start)
  • Limited API (no file system, no native Node.js modules)
  • Size limit (1 MB)

Limitations:

You cannot use Prisma, heavy npm packages or access the database directly in Middleware. For complex authorization logic it is better to verify the token in middleware and perform detailed permission checks in server components or Server Actions.

Chaining Actions

Middleware can perform multiple actions sequentially:

tsx
export function middleware(request: NextRequest) { const response = NextResponse.next() // 1. Logging console.log(`${request.method} ${request.nextUrl.pathname}`) // 2. Adding a header response.headers.set('x-pathname', request.nextUrl.pathname) // 3. A/B testing if (!request.cookies.get('ab-variant')) { response.cookies.set('ab-variant', Math.random() > 0.5 ? 'A' : 'B') } return response }

Useful Resources

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?
Practice Problems