Middleware in Next.js: Simplest Explanation

In Next.js, we can use file-based routing to create routes. Any folder inside the app directory with a page file will act as a route and when a user requests a route, its page file is rendered as a response.

So suppose you have a folder named “users” inside the app directory with a page.tsx file (for TypeScript) having a list of users as a component to be rendered. If a client opens the “/users” route, the list of users is rendered as a response. What if we don’t want to render this list of users to everyone? Here comes the middleware to intercept requests, apply logic, and decide how to proceed next.

In our previous tutorial, we learned how to add layouts in Next.js. In this tutorial, we will understand middleware in detail.

Introduction to Middleware

Middleware is used to intercept incoming requests and perform some actions before requests are completed. The action can be altering the response, redirecting to some route, modifying headers, etc.

For example, in the above case we can use middleware to check for the IP address of incoming requests and if it is an authorised person then only render the user/page.tsx otherwise we render an error message or redirect to another page. 

Creating Middleware in Next.js

To create the middleware, create a file called middleware.ts at the root of the project folder.

Example:

Run the below command in the terminal to create a new Next.js application:

npx create-next-app@latest

Check Yes for both TypeScript and App Router to follow along with us:

npx create-next-app@latest output

If you have any problems using Next.js, see Next.js Installation and Getting Started with Next.js.

Now open the project inside a code editor, remove all the files inside the app directory and create a file page.tsx for our main page with the following code:

const main = () => {
    return <h1>Main Page</h1>;
};

export default main;

Now let’s create another route ‘/home’ inside the app directory by creating a folder called home with a page.tsx file with the following code:

const home = () => {
    return <h1>Home Page</h1>;
};
    
export default home;

Now create a file middleware.ts inside the project folder (not in the app) with the following code:

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

export function middleware(request: NextRequest) {
    const response = NextResponse.rewrite(new URL('/home', request.url));
    return response;
}

This rewrites the URL internally to ‘/home’, not redirects. This means the URL is still the same as the user requested but the route gets changed.

Execute the below command to run the project:

npm run dev
For home page
For test page

See here we are trying to access the main page and test (which does not exist) but we get the home page in response, it means our middleware successfully intercepts the request and redirects it to the ‘/home’ route.

Matching Paths

By default, the middleware is called for every route in the project so it is important to specify which route to be used by middleware. For this we have matchers.

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

For multiple paths:

export const config = {
  matcher: ['/', '/about'],
}

Example:

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

export function middleware(request: NextRequest) {
    const response = NextResponse.rewrite(new URL('/home', request.url));
    return response;
}

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

See if we try to access the ‘/test’ route, the middleware will not redirect us.

Test page error

Conditional Statements

There is only one middleware.ts or middleware.js, so we can use condition statements to handle requests for multiple routes differently.

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

// Simulated authentication status for example
const isAuthenticated = false; 

export function middleware(request: NextRequest) {

  if (request.nextUrl.pathname === '/') {
    // Redirect to /home
    const response = NextResponse.rewrite(new URL('/home', request.url));
    return response;
  }

  if (/^\/admin/.test(request.nextUrl.pathname)) {
    // Check authentication status
    if (!isAuthenticated) {
      // Redirect to /login if not authenticated
      const response = NextResponse.rewrite(new URL('/login', request.url));
      return response;
    } else {
      // Redirect to requested page
      return NextResponse.rewrite(request.url);
    }
  }
}

export const config = {
  matcher: ['/', '/admin/:path*'],
}

In the first “if” we use strict equality operators, but in the second we use regular expressions to check all routes that start with “admin”.

Home page using conditional statements
Admin page using conditional statements
URL starts with admin

Setting Headers and Cookies

We can also see headers and cookies in the middleware using the NextResponse API.

if (/^\/admin/.test(request.nextUrl.pathname)) {
    // Check authentication status
    if (!isAuthenticated) {
      // Redirect to /login if not authenticated
      const response = NextResponse.rewrite(new URL('/login', request.url));
      // Set custom header
      response.headers.set('x-error-message', 'Unauthorized User');
      // Set cookie for unauthorized access
      response.cookies.set('user-access', 'denied');
      return response;
    } else {
      // Redirect to requested page
      return NextResponse.rewrite(request.url);
    }
  }
Setting headers
Setting cookies

Producing Response

Additionally, we can also send responses from middleware.

if (request.nextUrl.pathname === '/api/helloworld') {
  return NextResponse.json({ message: 'Hello, World!' });
}
Producing JSON response

*What’s Next?

Our next tutorial will cover a more advanced topic: pre-rendering and data fetching in Next.js, so check it out for the easiest explanation.

Summary

In short, the middleware is the code that executes for each page in a Next.js application to perform certain actions like changing the response, redirecting, modifying headers, producing responses etc.

We can create a middleware by creating a file called middleware.ts or middleware.js inside the root of your project (not in the app directory) and that’s it, we are ready to handle all the upcoming requests and responses on our application.

Reference

https://nextjs.org/docs/app/building-your-application/routing/middleware

Aditya Gupta
Aditya Gupta
Articles: 158