Middleware in NextJS 12 - What are they and how to get started with them

Middleware in NextJS 12 - What are they and how to get started with them

·

5 min read

Featured on daily.dev

Yesterday, we had the NextJS Conf and we got a lot of new things. NextJS 12 has been released and it has got a ton of new and exciting features. One of these features is middleware in NextJS so let us see how it works with an example.

What is NextJS Middleware?

Middleware are simple pieces of code that allows one to modify the response to a request even before it is completed. We can rewrite, redirect, add headers or even stream HTML based on the user's request.

This definition is from the NextJS Middleware Docs

Getting Started with NextJS 12

I am going to do everything in Replit for this example. You can also follow along with me on this tutorial.

The Repl - replit.com/@AnishDe12020/NextJS-12-Middleware

Creating a NextJS project with create-next-app will give us a project with NextJS 11 set up (as of 27th October 2021) so, firstly, we need to upgrade to NextJS 12.

With NPM -

npm install next@latest

With Yarn -

yarn add next@latest

A simple middleware

Now, to add middleware, we need to make a _middleware.js file in our pages directory. Note that you can also add middleware files in sub-directories in the pages directory to make it run after the top-level middleware file. You can refer to the NextJS Middleware Execution Order Documentation for more information.

Now let us write a simple middleware function -

const middleware = (req, ev) => {
  return new Response(req.ua.os.name);
};

export default middleware;

This will show the operating system of the user on the page. We were streaming HTML to the user but now let us see how we can rewrite routes under the hood.

A middleware that rewrites the route under the hood

First, we will create a new file called [os].js and copy in the index.js code into there, just replacing the function name.

[os].js -

import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'

export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>
          Welcome to <a href="https://nextjs.org">Next.js!</a>
        </h1>
      </main>

      <footer className={styles.footer}>
        <a
          href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          Powered by Vercel
            and Replit
        </a>
      </footer>
    </div>
  )
}

Now let us go back to our _middleware.js file and make some changes. First we will import NextResponse

import {NextResponse} from "next/server"

Now, let us store the user's operating system in a variable

const os = req.ua.os.name

Now, we can rewrite the URL to the new route we created and that is where NextResponse comes in.

return NextResponse.rewrite(`/${os}`) // This return is necessary

This is how out _middleware.js files should look now -

import {NextResponse} from "next/server"

const middleware =  (req, ev) => {
  const os = req.ua.os.name

  return NextResponse.rewrite(`/${os}`)
};

export default middleware;

Now let us import Next Router in our new route

import {useRouter} from "next/router"

As this is a react hook, we need to create a new instance of the NextJS router inside of the function being returned.

const router = useRouter()

We can get the operating system of the user from the URL parameter we rewrote earlier in the middleware

const os = router.query.os

At last, let us show the user what operating system they are using

<h1 className={styles.title}>
      You are using the {os} operating system
</h1>

At last this is how are [os].js should look like -

import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
import {useRouter} from "next/router"

export default function Os() {
  const router = useRouter()

  const os = router.query.os
  return (
    <div className={styles.container}>
      <Head>
        <title>Create Next App</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.main}>
        <h1 className={styles.title}>
          You are using the {os} operating system
        </h1>
      </main>

      <footer className={styles.footer}>
        <a
          href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
          target="_blank"
          rel="noopener noreferrer"
        >
          Powered by Vercel
            and Replit
        </a>
      </footer>
    </div>
  )
}

Now if we visit the home page, we should see this (note that I had removed some of the nextjs boilerplate code) -

image.png

I can emulate the browser user agent using the browser dev tools and we get a different result for the operating system -

image.png

Now, note that the URL is https://nextjs-12-middleware.anishde12020.repl.co/ and not https://nextjs-12-middleware.anishde12020.repl.co/Linux or https://nextjs-12-middleware.anishde12020.repl.co/Mac Os. This is because, we are re-writing the request in the middleware and hence there is no change on the client side.

How does NextJS middleware work under the hood?

NextJS middleware make use of Vercel's Edge Functions which run on the V8 Engine. The V8 Engine is a javascript engine written in C++ and is maintained by Google. It is considerably faster than running NodeJS on a virtual machine or a container and Vercel claims these edge functions to be instantaneous.

Limitations of edge functions

Some NodeJS APIs are not available with middleware (as they are edge functions) such as the filesystem API. Also, some NodeJS modules won't work as only modules which have implemented ES Modules and do not use any native NodeJS APIs are allowed. For more information, see here

Wrapping Up

So far, we have seen the power of middleware and how this is going to be a huge feature for developers. Things like serving localized sites, providing region-based discounts, authentication, bot detection and much more can be done quickly and easily by using middleware.

The team at Vercel has also put together many examples using middleware. Check them out here

That is it for this post, feel free to leave down a comment below and you can also reach out to me on Twitter

Did you find this article valuable?

Support Anish De by becoming a sponsor. Any amount is appreciated!