Middleware

Intercept and modify requests before they reach your pages

🛡️ What is Middleware?

Middleware runs before every request completes, allowing you to modify responses, redirect users, add headers, or check authentication. It's perfect for protecting routes and handling cross-cutting concerns.


// middleware.js
import { NextResponse } from 'next/server'

export function middleware(request) {
  // Check authentication
  const isLoggedIn = request.cookies.get('token')
  
  if (!isLoggedIn) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
}
                                    

Key Concepts

🔒

Route Protection

Guard pages before they load

if (!auth) {
  return NextResponse.redirect()
}
🌍

Edge Runtime

Runs globally at the edge

export const config = {
  runtime: 'edge'
}
🎯

Path Matching

Target specific routes

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

Request Modification

Add headers, cookies, rewrites

response.headers.set('x-custom', 'value')

🔹 Basic Middleware Setup

Create a middleware.js file in your project root. This file will run before every request to your application.

// middleware.js (in project root)
import { NextResponse } from 'next/server'

export function middleware(request) {
  console.log('Middleware running for:', request.url)
  
  // Continue to the requested page
  return NextResponse.next()
}

// Optional: Configure which paths to run middleware on
export const config = {
  matcher: '/:path*' // Run on all paths
}

🔹 Authentication Check

Use middleware to protect routes by checking if users are authenticated before allowing access to protected pages.

// middleware.js
import { NextResponse } from 'next/server'

export function middleware(request) {
  const token = request.cookies.get('auth-token')
  const { pathname } = request.nextUrl

  // Protect dashboard routes
  if (pathname.startsWith('/dashboard')) {
    if (!token) {
      // Redirect to login if not authenticated
      return NextResponse.redirect(new URL('/login', request.url))
    }
  }

  return NextResponse.next()
}

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

🔹 Adding Custom Headers

Middleware can add custom headers to responses, useful for security headers, CORS, or custom metadata.

// middleware.js
import { NextResponse } from 'next/server'

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

  // Add custom headers
  response.headers.set('x-custom-header', 'my-value')
  response.headers.set('x-request-time', new Date().toISOString())

  // Add security headers
  response.headers.set('X-Frame-Options', 'DENY')
  response.headers.set('X-Content-Type-Options', 'nosniff')

  return response
}

🔹 Redirects and Rewrites

Middleware can redirect users to different pages or rewrite URLs to serve different content without changing the browser URL.

// middleware.js
import { NextResponse } from 'next/server'

export function middleware(request) {
  const { pathname } = request.nextUrl

  // Redirect old URLs to new ones
  if (pathname === '/old-page') {
    return NextResponse.redirect(new URL('/new-page', request.url))
  }

  // Rewrite: Show /blog content when user visits /articles
  if (pathname.startsWith('/articles')) {
    return NextResponse.rewrite(new URL('/blog', request.url))
  }

  return NextResponse.next()
}

🔹 Geolocation and Localization

Access user location data in middleware to provide localized content or redirect based on geographic location.

// middleware.js
import { NextResponse } from 'next/server'

export function middleware(request) {
  const country = request.geo?.country || 'US'
  const { pathname } = request.nextUrl

  // Redirect based on country
  if (pathname === '/' && country === 'FR') {
    return NextResponse.redirect(new URL('/fr', request.url))
  }

  // Add country to headers
  const response = NextResponse.next()
  response.headers.set('x-user-country', country)

  return response
}

🔹 Path Matchers

Use the matcher config to specify exactly which paths should run your middleware, improving performance by avoiding unnecessary runs.

// middleware.js
import { NextResponse } from 'next/server'

export function middleware(request) {
  // Your middleware logic
  return NextResponse.next()
}

// Multiple path patterns
export const config = {
  matcher: [
    '/dashboard/:path*',     // All dashboard routes
    '/api/protected/:path*', // Protected API routes
    '/admin/:path*',         // Admin routes
    '/((?!api|_next/static|_next/image|favicon.ico).*)', // All except these
  ]
}

🔹 Rate Limiting

Implement basic rate limiting in middleware to prevent abuse by tracking request counts per IP address.

// middleware.js
import { NextResponse } from 'next/server'

const rateLimit = new Map()

export function middleware(request) {
  const ip = request.ip || 'unknown'
  const now = Date.now()
  const windowMs = 60000 // 1 minute
  const maxRequests = 100

  // Get or create rate limit entry
  const userLimit = rateLimit.get(ip) || { count: 0, resetTime: now + windowMs }

  // Reset if window expired
  if (now > userLimit.resetTime) {
    userLimit.count = 0
    userLimit.resetTime = now + windowMs
  }

  userLimit.count++
  rateLimit.set(ip, userLimit)

  // Block if over limit
  if (userLimit.count > maxRequests) {
    return new NextResponse('Too Many Requests', { status: 429 })
  }

  return NextResponse.next()
}

🧠 Test Your Knowledge

Where should you create the middleware file?