Next.js Middleware: How it Works and 5 Real Use Cases

Since v. 12.0.0 (stable from 12.2.0), Next.js added Middleware. Middleware (as it is in many other frameworks) is a way to intercept the user's request before it reaches the actual page. Put simply…

image

What is Middleware in Next.js?

Since v. 12.0.0 (stable from 12.2.0), Next.js added Middleware. Middleware (as it is in many other frameworks) is a way to intercept the user's request before it reaches the actual page. Put simply, it's a piece of code that stands in the middle between the server and the front-end. Middleware runs before every request the user made (but it can be filtered to work on a specific page), can read data from the user request, execute a pipeline of functions and get back to the client.

How Middleware works in Next.js

Placing a **middleware.ts** file (that will export a middleware named function) in the root of your project every page and the API of your site will execute the middleware pipeline before getting to the page. The following is the most basic middleware, it simply logs the user request (so you can see what a request is composed by), do nothing else, and return to the page (using **NextResponse.next()** ):

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

export function middleware(request: NextRequest) {
  console.log(request);
  return NextResponse.next();
}

Other than getting back to the page with **next()** NextResponse can set and get cookies, set headers, and redirect or rewrite the request to another URL.

Also, middleware can run only on specific pages using a matcher or conditional statement based on the pathname found in the request
Since middleware runs before sending the page to the user, it will not affect how pages are rendered, so it works with CSR, SSR, SSG, ISR.

Leveraging the request through middleware will allow us to manipulate the response without server-side code in pages.

If you want to learn more about Middleware you can read the Next.js documentation

Advanced Features: Middleware | Next.js

Redirect and rewrite

Sometimes you change the URL of a page or (even worse) the full structure of a website section, but you don't want to lose your acquired SEO score. Most of the guides will tell you that you need to redirect your old URLs to new ones. You can easily implement it with middleware, by redirecting all the matching patterns somewhere else. For example, you may want to move from a structure like **/blog/date/post** to something like **/content/post**. Once the new structure is in place, execute the redirects using middleware:

// middleware.ts
import { NextResponse } from "next/server";

export function middleware(request) {
  const newurl = generateNewUrl(request.url);
  return NextResponse.redirect(new URL(newurl, request.url));
}

export const config = {
  matcher: "/blog/:path*",
};

function generateNewUrl(url) {
  //Logic for redirect
  //...
  return newurl;
}

A different case is rewriting the content (keeping the URL, but showing a different page). For example, you have a restaurant site with a page (let's call it /about) where you show opening days and time. For a short period, your timetable changes and you don't want to edit your about page, but also you don't want to change the address to it. Create a new page /about-temp with the temporary data and rewrite the /about page to /about-temp using middleware:

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

export function middleware (request: NextRequest) {
    const url = request.nextUrl.clone()
    url.pathname = '/about-temp'
    return NextResponse.rewrite(url)
  }
}

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

We are using clone() to keep querystring and other url part unchanged

Authentication

From basic user/password, using 401 status in this way:

import { NextRequest, NextResponse } from "next/server";

export function middleware(req) {
  const basicAuth = req.headers.get("authorization");

  if (basicAuth) {
    const auth = basicAuth.split(" ")[1];
    const [user, pwd] = Buffer.from(auth, "base64").toString().split(":");

    if (user === "user" && pwd === "password") {
      return NextResponse.next();
    }
  }

  return new Response("Auth required", {
    status: 401,
    headers: {
      "WWW-Authenticate": 'Basic realm="Secure Area"',
    },
  });
}

To more complex authorization/authentication schema. Since you can read headers and cookies from the request you can probably use them to authenticate. In the following example, we are reading a Bearer authentication token, if the user is authorized we will show the page, otherwise, we will redirect the client to a sign-in page:

import { NextRequest, NextResponse } from "next/server";

export function middleware(req) {
  const basicAuth = req.headers.get("authorization");
  //auhorization: Beaer <token>
  if (basicAuth) {
    const auth = basicAuth.split(" ")[1];
    const token = Buffer.from(auth, "base64").toString().split(" ");
    const validToken = veryToken(token);

    if (validToken) {
      return NextResponse.next();
    }
  }

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

function validateToken(token) {
  //logic to validate token
  return true;
}

export const config = {
  matcher: "/dashboard/:path*",
};

Staging

While developing a site, you may want to show the users an 'Under Construction' page, but at the same time, you need to show your customer the progress of the site. You can give a key to place at the end of the URL, have the middleware, on the first visit, check for the key presence, and set a session cookie if the key is present, all the other requests will check for the cookie. If the cookie is not present the user will be redirected (or rewrite) to the 'Under Construction' page:

import { NextResponse } from 'next/server';

export function middleware(request) {
  const key=req.nextUrl.searchParams.get('secretkey');
  const cookie = request.cookies.get('auth')
  if (key) {
     const response = NextResponse.next();
     response.cookies.set('auth', 'OK');
     return response;
  }
  if (cookie===OK) {
    return NextResponse.next();
  }
  return NextResponse.redirect('/under-construction', request.url));
}

Personalization

Based on cookies (or other request params) you can redirect your user to different pages. Let's assume you have an e-commerce and segmented your customers into four groups. You can create 4 different homepages with different offers and CTA and show them to the user based on their group:

import { NextResponse } from "next/server";

export function middleware(request) {
  const cookie = request.cookies.get("group");
  if (group === 1) {
    const url = request.nextUrl.clone();
    url.pathname = "/offer-1";
    return NextResponse.rewrite(url);
  }
  if (group === 2) {
    const url = request.nextUrl.clone();
    url.pathname = "/offer-2";
    return NextResponse.rewrite(url);
  }
  if (group === 3) {
    const url = request.nextUrl.clone();
    url.pathname = "/offer-3";
    return NextResponse.rewrite(url);
  }
  if (group === 4) {
    const url = request.nextUrl.clone();
    url.pathname = "/offer-4";
    return NextResponse.rewrite(url);
  }

  //User with no cookie will get default homepage
  return NextResponse.next();
}

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

BOT Blocking

import { NextRequest, NextResponse } from "next/server";

export default async function middleware(req: NextRequest) {
  const bot = checkBot(req.headers.user - agent);

  if (bot === true) {
    const url = req.nextUrl;
    url.pathname = `/404`;
    return NextResponse.rewrite(url);
  }
  return NextResponse.next();
}

function checkBot(useragent) {
  //logic to check for a bot user agent
  return true;
}

Since you can read User-Agent, you can redirect BOT and harvesters to a 404 page:

More uses

Since you can access client requests (which include headers, cookies, geolocation, and more data) you can perform a lot of actions before showing the user the page. You can really personalize deeply based on language, cookies, location, the page request itself. You can log requests for statistics and debug, count access to pages, and more. Once deployed to Vercel, middleware functions are extremely fast since they are deployed on Edge. Way faster than having the same code at the page level, without considering that being a level above, they have access to more request data and they do not need to be replicated for every page.

Enjoyed this article?

Share it with your network to help others discover it

Continue Learning

Discover more articles on similar topics