How to Use Middleware in Nextjs

How to Use Middleware in Next.js Next.js has revolutionized the way developers build modern web applications by combining server-side rendering, static site generation, and client-side hydration into a seamless developer experience. One of the most powerful yet underutilized features introduced in Next.js 12 is Middleware . Middleware allows developers to run code before a request is completed, en

Oct 30, 2025 - 13:22
Oct 30, 2025 - 13:22
 1

How to Use Middleware in Next.js

Next.js has revolutionized the way developers build modern web applications by combining server-side rendering, static site generation, and client-side hydration into a seamless developer experience. One of the most powerful yet underutilized features introduced in Next.js 12 is Middleware. Middleware allows developers to run code before a request is completed, enabling dynamic behavior such as authentication, redirection, headers modification, and request rewriting all without touching the core application logic.

Unlike traditional server-side frameworks where middleware is tied to backend routes, Next.js Middleware operates at the edge running on CDN nodes closer to the user. This means your logic executes faster, reduces server load, and improves overall performance. Whether youre building a SaaS platform, an e-commerce site, or a content-heavy blog, leveraging Middleware can dramatically enhance security, user experience, and scalability.

In this comprehensive guide, well walk you through everything you need to know to effectively use Middleware in Next.js. From setting up your first middleware file to implementing advanced use cases like geolocation-based routing and A/B testing, youll gain the skills to optimize your Next.js applications with confidence.

Step-by-Step Guide

1. Understanding Middleware in Next.js

Middleware in Next.js is a function that runs before a request is completed. It sits between the incoming request and the page rendering process. Unlike serverless functions or API routes, Middleware runs on the Edge Runtime a lightweight environment powered by Web Standards and V8 isolates making it ideal for low-latency operations.

Middleware can be used to:

  • Modify incoming requests (headers, cookies, URL paths)
  • Redirect users based on conditions (geolocation, user agent, authentication)
  • Rewrite URLs dynamically
  • Add or modify response headers
  • Block malicious requests before they reach your application

Middleware files are placed in the root of your project directory and named middleware.js or middleware.ts. Next.js automatically detects and registers them without requiring any configuration.

2. Creating Your First Middleware File

To get started, navigate to the root directory of your Next.js project (the same level as pages or app, depending on your version). Create a new file called middleware.js (or middleware.ts if youre using TypeScript).

Open the file and add the following basic structure:

javascript

export function middleware(request) {

console.log('Middleware executed for:', request.url);

}

Save the file and restart your development server. Open your browser and navigate to any page. Youll see the log message appear in your terminal, confirming that Middleware is active.

Important: Middleware runs on every request by default including static assets like images, CSS, and JavaScript files. This can lead to unnecessary performance overhead if not properly scoped.

3. Configuring Middleware Matchers

To improve performance and target specific routes, use the config object to define which paths your Middleware should run on. This is done by exporting a config property alongside your middleware function.

For example, if you only want Middleware to run on routes under /dashboard and /api, configure it like this:

javascript

export function middleware(request) {

console.log('Running middleware on:', request.url);

}

export const config = {

matcher: ['/dashboard/:path*', '/api/:path*'],

};

The matcher array accepts path patterns using the same syntax as Next.jss pages router. Here are common patterns:

  • /:path* matches any route with one or more segments
  • /dashboard/:path* matches all routes under /dashboard
  • /api/:path* matches all API routes
  • /(en|es)/:path* matches routes starting with /en/ or /es/
  • !/api/:path* excludes API routes (useful for negation)

Always use matchers to limit scope. Running Middleware on every asset request can slow down your site.

4. Using the Request and NextResponse Objects

The middleware function receives a request object an instance of the standard Request Web API and you can return a NextResponse object to modify the response.

Heres how to import and use NextResponse:

javascript

import { NextResponse } from 'next/server';

export function middleware(request) {

const url = request.nextUrl;

// Example: Redirect all requests from /old-page to /new-page

if (url.pathname === '/old-page') {

return NextResponse.redirect(new URL('/new-page', request.url));

}

// Example: Add a custom header

const response = NextResponse.next();

response.headers.set('X-Middleware-Modified', 'true');

return response;

}

export const config = {

matcher: ['/old-page', '/:path*'],

};

The request.nextUrl property gives you access to the URL object, which allows you to manipulate the path, search params, and hostname programmatically.

You can also modify headers, cookies, and status codes:

javascript

export function middleware(request) {

const response = NextResponse.next();

response.headers.set('Cache-Control', 'no-cache');

response.cookies.set('visited', 'true', { httpOnly: true });

return response;

}

5. Implementing Authentication with Middleware

One of the most common use cases for Middleware is protecting routes based on user authentication status. Instead of checking auth in every page component, you can centralize this logic in Middleware.

Assume youre using cookies to store a JWT token named authToken. Heres how to block unauthenticated users from accessing protected routes:

javascript

import { NextResponse } from 'next/server';

export function middleware(request) {

const { pathname } = request.nextUrl;

const authToken = request.cookies.get('authToken')?.value;

// Allow public routes

const publicPaths = ['/', '/login', '/signup', '/api/auth'];

if (!publicPaths.includes(pathname) && !authToken) {

return NextResponse.redirect(new URL('/login', request.url));

}

}

export const config = {

matcher: ['/dashboard/:path*', '/profile/:path*', '/account/:path*'],

};

This approach ensures that any attempt to access /dashboard, /profile, or /account without a valid token will be redirected to /login before the page even begins to load.

6. Geolocation-Based Redirection

Next.js Middleware can access geolocation data via the request.geo property provided your hosting provider (like Vercel) supports it. This allows you to serve region-specific content or redirect users based on country.

Example: Redirect users from restricted countries to a landing page:

javascript

import { NextResponse } from 'next/server';

export function middleware(request) {

const { geo } = request;

const { pathname } = request.nextUrl;

// List of restricted countries (ISO 3166-1 alpha-2 codes)

const restrictedCountries = ['CN', 'IR', 'KP', 'SY'];

// Only apply to non-API routes

if (pathname.startsWith('/api')) return;

// Check if user is from a restricted country

if (restrictedCountries.includes(geo?.country || '')) {

return NextResponse.redirect(new URL('/restricted', request.url));

}

}

export const config = {

matcher: ['/dashboard/:path*', '/pricing', '/account/:path*'],

};

This is especially useful for compliance with regional laws (e.g., GDPR, sanctions) or for offering localized pricing.

7. A/B Testing with Middleware

Middleware can be used to assign users to different variants of a feature without modifying your frontend code. For example, you might want to show 20% of users a new UI layout.

Heres how to implement a simple A/B test:

javascript

import { NextResponse } from 'next/server';

export function middleware(request) {

const { pathname } = request.nextUrl;

const userId = request.cookies.get('userId')?.value || generateUserId();

// Only apply to homepage

if (pathname !== '/') return;

// Assign user to variant A (80%) or B (20%)

const variant = parseInt(userId.substring(0, 2), 16) % 100

// Set cookie for persistence

const response = NextResponse.next();

response.cookies.set('abVariant', variant, { maxAge: 60 * 60 * 24 * 30 }); // 30 days

return response;

}

function generateUserId() {

return Math.random().toString(16).substring(2, 10);

}

export const config = {

matcher: ['/'],

};

On the frontend, you can read the abVariant cookie and render different components accordingly.

8. Rewriting URLs Dynamically

Middleware can rewrite URLs without redirecting the user meaning the browsers address bar stays the same, but the server serves different content.

Example: Rewriting language codes in URLs:

javascript

import { NextResponse } from 'next/server';

export function middleware(request) {

const { pathname } = request.nextUrl;

// If URL starts with /en or /es, do nothing

if (pathname.startsWith('/en/') || pathname.startsWith('/es/')) return;

// Default to English if no language specified

const lang = request.cookies.get('lang')?.value || 'en';

const newPathname = /${lang}${pathname};

// Rewrite to the new path

const url = request.nextUrl.clone();

url.pathname = newPathname;

return NextResponse.rewrite(url);

}

export const config = {

matcher: ['/((?!_next|_vercel|favicon.ico).*)'],

};

This allows you to maintain clean URLs like /about while internally serving /en/about ideal for multilingual sites.

9. Handling API Routes with Middleware

Middleware can also protect or modify requests to API routes. For example, you might want to validate API keys or rate-limit requests before they hit your functions.

Example: API key validation:

javascript

import { NextResponse } from 'next/server';

export function middleware(request) {

const { pathname } = request.nextUrl;

// Only apply to API routes

if (!pathname.startsWith('/api')) return;

const apiKey = request.headers.get('x-api-key');

// Block requests without valid API key

if (!apiKey || apiKey !== process.env.API_SECRET) {

return NextResponse.json(

{ error: 'Invalid API key' },

{ status: 401 }

);

}

}

export const config = {

matcher: ['/api/:path*'],

};

This keeps your API logic clean you no longer need to validate keys in every route handler.

10. Debugging and Testing Middleware

Since Middleware runs on the Edge, debugging can be tricky. Here are some tips:

  • Use console.log() logs appear in your terminal during development
  • Check the Network tab in DevTools look for headers like X-Middleware-Request
  • Test redirects and rewrites with curl or Postman
  • Deploy to a preview environment on Vercel to test real-world behavior

Remember: Middleware does not run during static generation (SSG) or incremental static regeneration (ISR). It only runs on dynamic requests.

Best Practices

1. Always Use Matchers

Never leave your Middleware unscoped. Running it on every request including static assets adds unnecessary latency and increases edge function usage. Always define precise matcher patterns to target only the routes you need.

2. Keep Middleware Lightweight

Middleware runs on the Edge Runtime, which has limited memory and execution time (typically 510ms). Avoid heavy operations like database queries, external API calls, or large JSON parsing. If you need to fetch data, consider using a cache (like Redis) or precomputing values at build time.

3. Avoid Circular Redirects

Be careful when using redirects in Middleware. For example, if you redirect /login to /login, youll create an infinite loop. Always include the target route in your public paths list:

javascript

const publicPaths = ['/', '/login', '/signup'];

if (!publicPaths.includes(pathname) && !authToken) {

return NextResponse.redirect(new URL('/login', request.url));

}

4. Use Environment Variables for Configuration

Store sensitive data like API secrets, redirect URLs, or country codes in environment variables (.env.local), not hardcoded in your Middleware file.

5. Test Across Environments

Middleware behavior can differ between local development and production (especially with geolocation or headers). Always test on Vercel preview deployments or other production-like environments.

6. Monitor Edge Function Usage

On Vercel, each Middleware execution counts toward your Edge Function usage quota. Use the Vercel Dashboard to monitor usage and optimize your matchers to reduce unnecessary runs.

7. Combine with Server Components and API Routes

Middleware is not a replacement for server components or API routes its a complement. Use Middleware for low-latency, high-volume logic (redirects, headers, auth checks), and use server components or API routes for complex business logic or data fetching.

8. Handle Edge Runtime Limitations

The Edge Runtime doesnt support Node.js modules like fs, path, or crypto in the same way as Node.js. Use Web Standards instead:

  • Use TextEncoder and TextDecoder instead of Buffer
  • Use crypto.subtle for hashing
  • Use URL and URLSearchParams for URL manipulation

9. Version Your Middleware

As your Middleware grows in complexity, consider organizing it into separate files or modules. You can export multiple functions and use a main middleware.js as a coordinator:

javascript

// middleware/auth.js

export function checkAuth(request) {

// auth logic

}

// middleware/geo.js

export function checkGeo(request) {

// geo logic

}

// middleware.js

import { checkAuth } from './middleware/auth';

import { checkGeo } from './middleware/geo';

export function middleware(request) {

checkAuth(request);

checkGeo(request);

}

10. Document Your Middleware

As your team grows, ensure your Middleware is well-documented. Include comments explaining:

  • Why the logic exists
  • What conditions trigger it
  • Which routes it affects
  • Any dependencies or assumptions

Tools and Resources

1. Vercel Dashboard

If youre deploying on Vercel, the Dashboard provides detailed analytics on Edge Function usage, execution time, and request volume. This helps you identify inefficient Middleware and optimize your matchers.

2. Next.js Documentation

The official Next.js Middleware Guide is the most authoritative source for syntax, supported APIs, and limitations. Bookmark it for reference.

3. Edge Runtime API Reference

Since Middleware runs on the Edge Runtime, understanding the Web Standards it supports is crucial. Refer to the MDN Web Docs for details on Request, Response, Headers, and URL.

4. Next.js Middleware Examples Repository

GitHub hosts several community-driven repositories with real-world Middleware examples. Check out Vercels Examples for production-ready code snippets.

5. Postman and curl

Use Postman or command-line tools like curl to test how your Middleware modifies headers, redirects, or rewrites:

bash

curl -H "x-api-key: invalid-key" http://localhost:3000/api/protected

6. ESLint and TypeScript

Use TypeScript to catch errors early. Install the required types:

bash

npm install --save-dev @types/node

And configure ESLint to enforce best practices around Middleware usage.

7. LogRocket or Sentry

For production monitoring, integrate logging tools to capture Middleware errors, redirects, and performance metrics. This helps you detect unexpected behavior in real time.

8. Next.js Community Discord

Join the Next.js Discord to ask questions, share solutions, and learn from other developers using Middleware in production.

Real Examples

Example 1: Multi-Language Site with Language Detection

Scenario: Youre building a blog with support for English, Spanish, and French. You want to automatically redirect users based on their browser language preference.

javascript

import { NextResponse } from 'next/server';

export function middleware(request) {

const { pathname } = request.nextUrl;

const browserLang = request.headers.get('Accept-Language')?.split(',')[0]?.slice(0, 2);

// Skip if already on a language path

if (['/en', '/es', '/fr'].some(prefix => pathname.startsWith(prefix))) return;

// Default to English

let lang = 'en';

if (browserLang === 'es') lang = 'es';

if (browserLang === 'fr') lang = 'fr';

// Rewrite to the appropriate language path

const url = request.nextUrl.clone();

url.pathname = /${lang}${pathname};

return NextResponse.rewrite(url);

}

export const config = {

matcher: ['/((?!_next|_vercel|favicon.ico).*)'],

};

Example 2: Rate Limiting API Endpoints

Scenario: You want to prevent abuse of your public API by limiting requests to 10 per minute per IP.

javascript

import { NextResponse } from 'next/server';

const RATE_LIMIT = 10;

const WINDOW_MS = 60 * 1000;

// In-memory cache (use Redis in production)

const requests = new Map();

export function middleware(request) {

const { pathname } = request.nextUrl;

if (!pathname.startsWith('/api/')) return;

const ip = request.headers.get('x-forwarded-for')?.split(',')[0] || request.ip;

const now = Date.now();

const key = ${ip}:${pathname};

const timestamps = requests.get(key) || [];

const recent = timestamps.filter(t => now - t

if (recent.length >= RATE_LIMIT) {

return NextResponse.json(

{ error: 'Too many requests' },

{ status: 429 }

);

}

// Store new timestamp

recent.push(now);

requests.set(key, recent);

return NextResponse.next();

}

export const config = {

matcher: ['/api/:path*'],

};

Note: This is a simplified version. For production, use Redis or another persistent cache.

Example 3: Feature Flags Based on User Role

Scenario: You want to enable a beta feature for users with a specific role stored in a cookie.

javascript

import { NextResponse } from 'next/server';

export function middleware(request) {

const { pathname } = request.nextUrl;

const role = request.cookies.get('userRole')?.value;

// Enable beta feature for admins

if (role === 'admin' && pathname === '/dashboard') {

const url = request.nextUrl.clone();

url.searchParams.set('beta', 'true');

return NextResponse.rewrite(url);

}

}

export const config = {

matcher: ['/dashboard'],

};

On the frontend, you can check for beta=true in the URL and render experimental components.

Example 4: Blocking Scrapers and Bots

Scenario: You want to block known bot user agents from accessing your site.

javascript

import { NextResponse } from 'next/server';

const blockedUserAgents = [

'bot',

'crawler',

'spider',

'scraper',

'python-requests',

'curl',

];

export function middleware(request) {

const userAgent = request.headers.get('User-Agent')?.toLowerCase();

if (userAgent && blockedUserAgents.some(bot => userAgent.includes(bot))) {

return NextResponse.json(

{ error: 'Access denied' },

{ status: 403 }

);

}

}

export const config = {

matcher: ['/'],

};

FAQs

Can Middleware access cookies and headers?

Yes. Middleware can read and modify both request and response headers, as well as cookies using the request.headers and NextResponse.cookies APIs.

Does Middleware work with static pages (SSG)?

No. Middleware only runs on dynamic requests. It does not execute during static generation or incremental static regeneration. Use getStaticProps or server components for static data fetching.

Can I use database queries in Middleware?

Technically yes, but its strongly discouraged. Edge Runtime has limited resources and timeouts. Use caching (Redis, Edge Cache) or move heavy logic to API routes.

How do I test Middleware locally?

Run npm run dev and check your terminal logs. Use browser DevTools to inspect headers and redirects. For full edge behavior, deploy to Vercel preview.

Is Middleware available in Next.js 13 and 14?

Yes. Middleware is fully supported in Next.js 13+ (App Router) and 14. The API remains consistent, though the file location may differ slightly if using app directory.

Can I use Middleware to add authentication tokens to requests?

Yes. You can modify the request headers before rewriting or redirecting. For example, inject a token into a header for an internal API call.

Does Middleware affect SEO?

Properly implemented Middleware improves SEO by ensuring users are redirected to the correct language or region, reducing duplicate content, and improving load speed. Avoid infinite redirects or incorrect canonical headers.

What happens if Middleware throws an error?

Errors in Middleware will cause the request to fail with a 500 status code. Always wrap logic in try-catch blocks and log errors for debugging.

Can I use Middleware with custom servers (Express, Koa)?

No. Middleware is a Next.js-specific feature and only works when using Next.jss built-in server. Custom servers bypass Next.jss routing system.

Is Middleware free on Vercel?

Yes, but it counts toward your Edge Function usage quota. Free plans include 100,000 executions/month. Paid plans offer higher limits.

Conclusion

Middleware in Next.js is a powerful, edge-based tool that empowers developers to handle complex routing, authentication, and optimization logic without bloating their application code. By running closer to the user, Middleware reduces latency, improves security, and enhances user experience all while keeping your server-side logic clean and maintainable.

In this guide, weve explored how to set up Middleware, configure matchers, implement authentication, geolocation, A/B testing, and API protection. Weve reviewed best practices to ensure performance and scalability, highlighted essential tools, and provided real-world examples that you can adapt to your projects.

As Next.js continues to evolve, Middleware will become even more integral to building high-performance web applications. Whether youre managing a global audience, enforcing compliance, or optimizing conversion rates, Middleware gives you the control to shape user interactions at the earliest possible point in the request lifecycle.

Start small implement a simple redirect or header modification today. Then, gradually layer in more advanced use cases. With careful planning and testing, Middleware can become the backbone of your Next.js applications architecture.