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
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
TextEncoderandTextDecoderinstead ofBuffer - Use
crypto.subtlefor hashing - Use
URLandURLSearchParamsfor 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.