DEV Community

Cover image for Next js Middleware – What Is It and When to Use It
Chris Lojniewski for Pagepro

Posted on • Originally published at pagepro.co

Next js Middleware – What Is It and When to Use It

Introduction – What’s Middleware?

Middleware is a fundamental concept in web development. It’s a function that sits between an incoming request and the final response, allowing you to process, modify, or handle the request before it reaches its destination. When working with Next.js Middleware, you can leverage this capability to handle various tasks at the edge of your application, before the request reaches your React components or API routes. It would help you build highly performant, secure, and optimized applications with minimal overhead.

Today, we will explain what Nextjs Middleware is, how it works, and when to use it effectively in your Next.js applications.

Understanding Middleware

Next js Middleware Response Processing

Middleware processes incoming requests by either passing the request forward, modifying it, or returning an early response. It acts as a chain where each piece of middleware performs its task before handing over the request to the next component. It’s commonly used across frameworks to perform repetitive tasks like validation, session management, or request logging. Custom Middleware built with Next.js extends these capabilities with the power of edge computing, enabling faster responses and greater control over the request-response lifecycle.

Next.js Middleware Basics

Important Note: In Next.js, only one middleware.ts file is supported per project. You can still use multiple source files, but only a single entry file is allowed. The middleware code is executed whenever a user requests any route within the Next.js application scope. At this point, you can choose to modify the request or pass it through unchanged. All operations are executed on the server side—in the Edge runtime, to be precise. This means that only APIs compatible with the Edge runtime can be used, so be cautious not to include any Node.js-specific APIs.

Advantages of using middleware in Next.js

Higher Performance of your web application

Middleware allows you to execute code as soon as a request is received, enabling you to filter out unnecessary calls, such as blocking unauthenticated users from certain routes. Running the code on the edge ensures that requests are processed with minimal latency, ensuring a faster request and response time.

Better Security with Middleware Function

Middleware is ideal for handling authentication, validation, and other security measures before a request reaches your main application logic. It prevents unauthorized access to sensitive routes and ensures that all security checks happen consistently and early in the request lifecycle.

Centralized Code

Implement middleware to enable a unified approach for handling tasks like setting custom headers or enforcing security policies. Instead of repeating code across multiple components, you can manage these rules in a single location, making the application more maintainable and reducing potential errors caused by code duplication.

Best Use Cases for Middleware in Next.js Project

Initial Authentication and Authorization

Middleware serves as an ideal gatekeeper for initial authorization checks before users access protected content. Verifying a valid token or simply checking for an authentication cookie ensures that only authorized users can reach sensitive routes. Unauthorized visitors can be redirected to a login page or shown a custom error message. This streamlines the access control process without adding client-side complexity.

Request Logging and Analytics

Utilizing middleware for logging allows seamless integration with third-party monitoring or analytics tools. All incoming requests can be captured, processed, and shaped before reaching your core application logic. It enables easy filtering of data, monitoring of user interactions, and the extraction of insights for performance optimization and anomaly detection.

SEO-Friendly Redirects

Handling redirects efficiently is another strong use case for middleware. It’s possible to redirect users from outdated URLs to new ones or implement location-based redirects based on the user’s region. Using middleware for redirects improves the user experience by guiding visitors to the correct content. It also maintains SEO health by setting the proper status codes and avoiding broken links.

Maintenance Mode

Middleware simplifies implementing maintenance modes during scheduled updates or unexpected outages. Incoming traffic can be redirected to a dedicated maintenance page or served a user-friendly message. This prevents users from encountering broken pages or errors. The ability to toggle maintenance mode easily ensures that the user experience remains uninterrupted, even during critical updates.

When to Use Next.js Middleware

I think it’s worth using middleware whenever you can. I don’t see any cons related to it. Middleware allows for boosting performance and eliminates code duplication. It does so by handling cross-cutting concerns like authentication tokens, localization, and redirects in one centralized location.

Most applications require user authentication, support multiple locales, or need URL redirects due to legacy links on forums or third-party sites. While middleware might add a bit of complexity, the payoff in streamlined logic and better application structure is worth it. If you have tools that can handle that in single place why not to use them?

Best Practices

Modularize Your Middleware

Since only a single middleware.ts file is allowed per project, organize your logic by splitting it into smaller, modular functions. Import these functions into the main middleware file to maintain clean, readable, and manageable code.

Optimize Code Execution with Conditionals

When handling multiple actions within middleware, use conditionals to ensure that only necessary logic is executed for specific paths or requests. This helps reduce runtime of the code execution and keeps the middleware efficient.

Leverage matcher Configuration

Use the matcher configuration option to define which routes the middleware logic should apply to. Specifying targeted routes prevents unnecessary executions and ensures the middleware only runs when required, reducing performance overhead.

Keep Middleware Lightweight

Avoid heavy computations or complex logic within middleware. Focus on quick, non-blocking actions like authentication, logging, or basic redirects. Rely on synchronous actions where possible to prevent adding latency or delays to requests.

Handle Errors and Edge Cases Gracefully

Middleware should include robust error handling to manage unexpected scenarios like token validation failures. Ensure that your middleware returns appropriate responses, redirects, or error messages to guide users through such cases, preventing any interruptions in the application’s flow.

Setup and Configuration

To start working with middleware you just need to create a single file middleware.ts (or .js) in the root of your project. You can start with sample code from documentation and check if it’s triggering with single console.log. Make sure to include your in the config.matcher!



import { NextResponse } from 'next/server'

import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
console.log(request);

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

}

export const config = {

matcher:["/((?!api|_next|public|static|.\..).*)"]

}

Enter fullscreen mode Exit fullscreen mode




Code Examples

I’ve prepared a few code examples for different middlewares:

Authentication middleware:



// middleware.ts

import { NextResponse } from "next/server";

import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {

const { pathname } = request.nextUrl;

const protectedRoutes = ["/dashboard", "/profile"];

if (protectedRoutes.some(route => pathname.startsWith(route))) {

const token = request.cookies.get("token");

if (!token) {

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

}
Enter fullscreen mode Exit fullscreen mode

}

return NextResponse.next();

}

// Apply middleware to all routes

export const config = {

matcher: ["/:path*"],

};

Enter fullscreen mode Exit fullscreen mode




Middleware for redirects/rewrites:




// middleware.ts

import { NextResponse } from 'next/server';

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

export function middleware(request: NextRequest) {

const { pathname } = request.nextUrl;

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

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

}

if (pathname === '/legacy-route') {

return NextResponse.redirect(new URL('/modern-route', request.url));
Enter fullscreen mode Exit fullscreen mode

}

return NextResponse.next();

}

export const config = {

matcher: ['/:path*'],

};

Enter fullscreen mode Exit fullscreen mode




Example of logging middleware:




import { NextResponse } from "next/server";

import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {

const { method, url, ip } = request;

logRequest(method, url, ip); // here is your logging function

return NextResponse.next();

}

export const config = {

matcher: ["/((?!_next|public|static|.\..).*)"],

};

Enter fullscreen mode Exit fullscreen mode




Conclusion

As you should have basic knowledge, common use cases and sample code – you are ready to go. Remember that they can bring a lot of benefits with a low cost. Try them out in your next project or check if you can fit them somewhere within your current one!

Read more

Top comments (0)