Skip to main content

Next.js Middleware Functions

Introduction

Middleware functions in Next.js allow you to run code before a request is completed. This powerful feature enables you to execute logic before a page or API route is rendered, making it perfect for tasks like authentication, logging, redirects, and modifying request or response objects.

In this tutorial, we'll explore what Next.js middleware is, how it works, and how to implement it effectively in your applications. By the end, you'll be able to leverage middleware to solve common web development challenges.

Understanding Next.js Middleware

What is Middleware?

Middleware in Next.js sits between the client and your application, allowing you to examine incoming requests and take action based on those requests before they complete. Unlike API routes or page components, middleware runs before any rendering happens.

Middleware Request Flow

Key Features of Next.js Middleware

  • Early access to requests: Executes before routing and rendering occur
  • Conditional execution: Can be configured to run on specific paths
  • Response modification: Can rewrite, redirect, or modify response headers
  • Cookie management: Can read and set cookies
  • Request inspection: Can access headers and URL information

Creating Your First Middleware

Basic Setup

In Next.js, you can create middleware by adding a file named middleware.js or middleware.ts in the root of your project. This file exports a function that runs for every request.

Let's create a simple middleware that logs the path of each request:

javascript
// middleware.js
export function middleware(request) {
console.log(`Accessing path: ${request.nextUrl.pathname}`);
}

This middleware will execute for every request made to your application and log the requested path.

Understanding the Request Object

The middleware function receives a request object, which is an extension of the standard Web Request object. It includes additional properties:

  • request.nextUrl: A parsed URL object with utility methods
  • request.cookies: An object containing the cookies sent with the request
  • request.headers: Headers associated with the request

Returning a Response

Middleware can return a response to:

  1. Redirect users
  2. Rewrite the request to another destination
  3. Set cookies or headers
  4. Return a direct response

Here's an example that redirects users from the /old-page path to /new-page:

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

export function middleware(request) {
if (request.nextUrl.pathname === '/old-page') {
return NextResponse.redirect(new URL('/new-page', request.url));
}
}

Configuring Middleware

Matching Paths

You can limit middleware execution to specific paths using the matcher configuration:

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

export function middleware(request) {
// Middleware logic here
}

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

This middleware will only run on paths starting with /api/.

You can use multiple matchers as well:

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

Using Regular Expressions

For more complex path matching, you can use regular expressions:

javascript
export const config = {
matcher: [
// Match all routes except those starting with:
// - api (API routes)
// - _next/static (static files)
// - _next/image (image optimization files)
// - favicon.ico (favicon file)
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
};

Practical Examples

Let's explore some common use cases for middleware in Next.js applications.

Example 1: Authentication Middleware

This middleware checks if a user is authenticated before allowing access to protected routes:

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

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

// Get authentication token from cookie
const token = request.cookies.get('auth-token')?.value;

// Protected routes start with /dashboard
if (pathname.startsWith('/dashboard')) {
// If no token exists, redirect to login
if (!token) {
return NextResponse.redirect(new URL('/login', request.url));
}
}

return NextResponse.next();
}

Example 2: Internationalization (i18n)

This middleware handles language detection and redirection:

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

const supportedLocales = ['en', 'fr', 'es'];
const defaultLocale = 'en';

export function middleware(request) {
// Get pathname from request URL
const { pathname } = request.nextUrl;

// Check if pathname already has a valid locale
const pathnameHasLocale = supportedLocales.some(
locale => pathname.startsWith(`/${locale}/`) || pathname === `/${locale}`
);

if (pathnameHasLocale) return;

// Get user's preferred language from request headers
const preferredLocale = request.headers.get('accept-language')?.split(',')[0].split('-')[0] || defaultLocale;
const locale = supportedLocales.includes(preferredLocale) ? preferredLocale : defaultLocale;

// Create new URL with locale
request.nextUrl.pathname = `/${locale}${pathname}`;
return NextResponse.redirect(request.nextUrl);
}

export const config = {
matcher: [
// Skip any paths that shouldn't be internationalized
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
};

Example 3: Rate Limiting

This example demonstrates a simple rate-limiting middleware:

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

// Simple in-memory store (use Redis or similar in production)
const rateLimit = {};

export function middleware(request) {
if (request.nextUrl.pathname.startsWith('/api')) {
const ip = request.ip || 'anonymous';

// Initialize or get the rate limit data
const currentTimestamp = Date.now();
if (!rateLimit[ip]) {
rateLimit[ip] = {
count: 0,
timestamp: currentTimestamp,
};
}

// Reset counter after 1 minute
if (currentTimestamp - rateLimit[ip].timestamp > 60000) {
rateLimit[ip] = {
count: 0,
timestamp: currentTimestamp,
};
}

// Increment request count
rateLimit[ip].count++;

// Check if rate limit reached
if (rateLimit[ip].count > 100) { // 100 requests per minute
return new NextResponse(JSON.stringify({ error: 'Rate limit exceeded' }), {
status: 429,
headers: { 'Content-Type': 'application/json' }
});
}
}

return NextResponse.next();
}

Example 4: Logging and Analytics

This middleware logs information about each request:

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

export function middleware(request) {
const start = Date.now();
const response = NextResponse.next();

// Add response handler to calculate duration after response is processed
response.headers.set('x-middleware-cache', 'no-cache');

console.log({
time: new Date().toISOString(),
path: request.nextUrl.pathname,
userAgent: request.headers.get('user-agent'),
duration: `${Date.now() - start}ms`,
});

return response;
}

Advanced Middleware Techniques

Chaining Middleware Functions

Although Next.js doesn't directly support middleware chaining like Express.js, you can simulate it by creating utility functions:

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

function withLogging(request) {
console.log(`Request: ${request.method} ${request.nextUrl.pathname}`);
return NextResponse.next();
}

function withAuthentication(request, response) {
const authToken = request.cookies.get('auth-token')?.value;
if (!authToken && request.nextUrl.pathname.startsWith('/dashboard')) {
return NextResponse.redirect(new URL('/login', request.url));
}
return response;
}

export function middleware(request) {
let response = withLogging(request);
response = withAuthentication(request, response);
return response;
}

Middleware with Edge Runtime

Next.js middleware runs on the Edge runtime by default, which is more lightweight and has faster startup times than Node.js. However, this means some Node.js APIs are not available.

You can specify runtime requirements:

javascript
// middleware.js
export const middleware = (request) => {
// Middleware code
};

// Specify which runtime to use
export const config = {
runtime: 'nodejs', // 'edge' (default) or 'nodejs'
matcher: '/*',
};

Troubleshooting Middleware

Common Issues and Solutions

  1. Middleware doesn't run: Ensure your middleware file is in the project root and named correctly (middleware.js or middleware.ts).

  2. Infinite redirect loops: Be careful with redirects in middleware, as they can cause infinite loops. Always check conditions carefully.

    javascript
    // Wrong - can cause infinite loop
    export function middleware() {
    return NextResponse.redirect(new URL('/destination', request.url));
    }

    // Correct - only redirect under specific conditions
    export function middleware(request) {
    if (request.nextUrl.pathname !== '/destination') {
    return NextResponse.redirect(new URL('/destination', request.url));
    }
    }
  3. Headers/cookies not being set: Remember that headers can only be modified in middleware when returning a NextResponse.

Debugging Tips

To debug middleware:

  1. Add console.log statements to track execution
  2. Use the matcher configuration to limit where middleware runs during testing
  3. Check the Network tab in DevTools to verify redirects and headers

Summary

Next.js middleware provides a powerful way to intercept requests before they complete, enabling a wide range of functionality from authentication to internationalization. By placing a single file in your project root, you can execute code that runs at the edge, checking conditions and modifying responses as needed.

Key points to remember:

  • Middleware runs before routing and rendering
  • It can redirect, rewrite, or modify response headers
  • Use the matcher configuration to limit where middleware runs
  • Middleware runs in the Edge runtime by default (but can use Node.js)
  • Common use cases include authentication, logging, and internationalization

Additional Resources

Practice Exercises

  1. Create a middleware that adds a custom security header to all responses
  2. Build an A/B testing middleware that directs users to different versions of a page
  3. Implement a middleware that checks user permissions based on their role
  4. Design a middleware that records page view analytics to a database

By mastering middleware functions, you'll be equipped to solve many common web development challenges directly within the Next.js framework, without needing additional server-side code.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)