middleware tricks

10/6/20243 min read

middleware tricks

middleware runs before your request completes. intercepts stuff before it hits the page. perfect for auth, redirects, logging, modifying requests/responses.

not great for heavy lifting though - keep it light. no complex data fetching, heavy compute, or direct db ops. use route handlers for that.

setup

create middleware.ts in your project root:

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url))
}

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

matching paths

middleware runs on every route by default. use matcher to target specific ones:

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

regex works too. exclude certain paths:

export const config = {
  matcher: [
    '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
  ],
}

NextResponse basics

redirect, rewrite, set headers and cookies:

import { NextResponse } from 'next/server'

export function middleware(request: NextRequest) {
  // redirect
  return NextResponse.redirect('/new-url')

  // or rewrite
  // return NextResponse.rewrite('/new-content')

  // or modify response
  // const response = NextResponse.next()
  // response.headers.set('X-Custom-Header', 'sup')
  // return response
}

cookies

simple cookie handling:

export function middleware(request) {
  // get cookie
  let cookie = request.cookies.get('nextjs')

  // check if exists
  const hasCookie = request.cookies.has('nextjs')

  // delete it
  request.cookies.delete('nextjs')

  // set new cookie
  const response = NextResponse.next()
  response.cookies.set('vercel', 'fast')

  return response
}

headers

add custom headers to requests or responses:

export function middleware(request: NextRequest) {
  const requestHeaders = new Headers(request.headers)
  requestHeaders.set('x-custom', 'hello')

  const response = NextResponse.next({
    request: { headers: requestHeaders },
  })

  response.headers.set('x-response', 'sup')
  return response
}

CORS handling

manage cross-origin requests:

const allowedOrigins = ['https://acme.com', 'https://my-app.org']

const corsOptions = {
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Authorization',
}

export function middleware(request: NextRequest) {
  const origin = request.headers.get('origin') ?? ''
  const isAllowedOrigin = allowedOrigins.includes(origin)

  // handle preflight
  if (request.method === 'OPTIONS') {
    const preflightHeaders = {
      ...(isAllowedOrigin && { 'Access-Control-Allow-Origin': origin }),
      ...corsOptions,
    }
    return NextResponse.json({}, { headers: preflightHeaders })
  }

  // handle simple requests
  const response = NextResponse.next()
  if (isAllowedOrigin) {
    response.headers.set('Access-Control-Allow-Origin', origin)
  }

  return response
}

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

early responses

respond directly without hitting your app:

import { isAuthenticated } from '@lib/auth'

export const config = {
  matcher: '/api/:function*',
}

export function middleware(request) {
  if (!isAuthenticated(request)) {
    return new Response(
      JSON.stringify({ success: false, message: 'nope' }),
      { status: 401, headers: { 'Content-Type': 'application/json' } }
    )
  }

  return NextResponse.next()
}

why use it

  • auth & authorization
  • session management
  • input validation & security
  • header manipulation
  • server-side redirects based on user role