DEV Community

Cover image for Using Next.js’ middleware and Edge Functions
Matt Angelosanto for LogRocket

Posted on • Originally published at blog.logrocket.com

Using Next.js’ middleware and Edge Functions

Written by Temitope Oyedele✏️

Middleware is regarded as one of the coolest features released in Next.js 12 because it has a lot of utility in modern applications, like working together with Vercel Edge Functions.

The term middleware is not a new one. Frameworks, like Express.js, use middleware to intercept an HTTP request and process it before it reaches the route handler.

So, in this post, we’ll learn how middleware works with Edge Functions and why it’s important to know. Now, let’s get started!

Next.js middleware

In Next.js, middleware is a simple piece of code that allows us to change the response to a request before it finishes. Based on a user's request, we can rewrite, redirect, add headers, or even stream HTML.

The exciting thing about using middleware is that it runs before every request made on Next.js. This means that when a user requests any page or API route, the middleware logic runs right before the request.

To create a middleware in Next.js, create a file called _middleware.js or _middleware.ts under pages or api. Within the file, export the middleware function, as shown below:

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

export default function middleware(
  request: NextRequest,
  event: NextFetchEvent,
) {
  // return your new response;
}
Enter fullscreen mode Exit fullscreen mode

Its API is built on native FetchEvent, Response, and Request objects. These native web API objects have been extended to provide you with more control over how you manipulate and configure a response based on incoming requests.

With the middleware function signature, the waitUntil() method is included in the NextFetchEvent object, which extends the native FetchEvent object:

import type { NextFetchEvent } from 'next/server';
import type { NextRequest } from 'next/server';
export type Middleware = (
  request: NextRequest,
  event: NextFetchEvent,
) => Promise<Response | undefined> | Response | undefined;
Enter fullscreen mode Exit fullscreen mode

After the response is sent, you can use the waitUntil() method to extend the function's execution. In practice, this means that if you have other background work to do, you can send a response and then continue the function execution.

Integrations with logging tools such as Sentry or DataDog are yet another factor why you might need waitUntil(). You can send logs of response times, errors, API call durations, or total performance metrics after the response has been sent.

The native Request object is extended by the NextRequest object, while the native Response object is extended by the NextResponse object.

Middleware gives us complete flexibility to run our code before a request completes. You can modify the response based on a user's incoming request by rewriting, redirecting, adding headers, or even streaming HTML.

Another advantage is that middleware provides a more efficient way to share logic between pages, allowing you to keep your code DRY and efficient.

However, middleware is still in beta, so you might encounter some bugs while working with it.

How does Next.js middleware work?

Vercel's Edge Functions, which run on the V8 Engine, are used by Next.js’ middleware. Google maintains the V8 Engine, a JavaScript engine written in C++, and it is significantly faster than running Node.js in a virtual machine or container, and Vercel claims that these Edge Functions are instantaneous.

The Edge Functions are placed between the user’s request and the server, so the middleware runs before that request completes.

Understanding Edge Functions

If you've ever used serverless functions, you'll understand Edge Functions. To get a better understanding, we’ll compare Edge Functions to serverless functions.

When you deploy a serverless function to Vercel, it’s deployed to a server somewhere in the world. The request made to that function will then execute where the server is located.

If a request is made to a server close to that location, it will happen quickly. But if a request is made to the server from a location very far away, then the response will be much slower.

This is where Edge Functions can help. In simplicity, Edge Functions are serverless functions that run geographically close to a user, making the request very fast regardless of where that user might be.

When deploying a Next.js application to Vercel, the middleware will deploy as Edge Functions to all regions around the world. This means that instead of a function sitting on a server, it will sit on multiple servers.

Here, the Edge Functions use middleware. One of the unique things about Edge Functions is that they are a lot smaller than our typical serverless functions. They also run on V8 runtime, which makes it 100x faster than Node.js in containers or virtual machines.

Why are Edge Functions and middleware important?

Understanding how to use middleware with Edge Functions solves the issue of sharing a common login across multiple applications such as authentication, bot protection, redirects, browser support, feature flags, A/B testing, server-side analytics, logging, and geolocations efficiently.

With the help of Edge Functions, middleware can run faster like static web applications because they help to reduce latency and eliminate cold startup.

With Edge Functions, we can run our files on multiple geolocations, allowing regions closest to the user to respond to the user’s request. This provides faster user requests regardless of their geographic location.

Traditionally, web content is served from a CDN to an end user to increase speed. However, because these are static pages, we lose dynamic content. Also, we use server-side rendering to get dynamic content from the server, but we lose speed.

However, by deploying our middleware to the Edge like a CDN, we bring our server logic closer to our visitors’ origin. As a result, we have speed as well as personalization for the users.

As a developer, you can now build and deploy your website and then cache the result in CDNs across the world.

Basic password authentication implementation

Let’s see how we can use middleware in our API for authentication by creating a basic password authentication and testing it with Postman.

Postman is an API platform that allows you to create and use APIs faster by streamlining collaboration and simplifying the API lifecycle.

First, open up the terminal and create a folder where we want our project installed:

mkdir next_middleware
Enter fullscreen mode Exit fullscreen mode

cd into the recently created folder, install Next.js, and give your project a name:

npx create-next-app@latest
Enter fullscreen mode Exit fullscreen mode

Now that we’ve done that, let’s create our middleware. Inside our pages/api folder, let’s create a file called _middleware.js. Next, let’s paste in the following:

import { 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 === 'mydmin' && pwd === 'mypassword') {
      return NextResponse.next()
    }
  }
  return new Response('Auth required', {
    status: 401,
    headers: {
      'WWW-Authenticate': 'Basic realm="Secure Area"',
    },
  })
}
Enter fullscreen mode Exit fullscreen mode

Inside the middleware function, we first get the authorization header; if the authorization header is set, we pass the username and password from the header and check if the user equals our parameters (username and password).

If they do, return the NextResponse and call the next function. What this next function does is return the NextResponse that continues the middleware chain. If the username and password did not match, then we return a new response saying authentication is required.

Let’s test this out using Postman by making an API request. But first, we need to start up our app:

 npm run dev
Enter fullscreen mode Exit fullscreen mode

Then, open Postman and make a new request; our request should be made to http://localhost:3000/api/hello.

Click on the authorization tab and select Basic Auth. Input the correct user credentials and this should be our result:

Basic Auth In Authorization Tab

And, when we input a wrong username or password, we get the following:

Authorization Required Error

We can see how easy it is to add basic authentication to API routes. By adding the middleware file, we get the authentication of all endpoints in our API. This is much easier and more convenient.

Conclusion

With its ability to solve basic problems like authentication and geolocation, middleware is an outstanding feature, and with the help of Edge Functions, middleware can run faster like static web applications.


LogRocket: Full visibility into production Next.js apps

Debugging Next applications can be difficult, especially when users experience issues that are hard to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket signup

LogRocket is like a DVR for web and mobile apps, recording literally everything that happens on your Next app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your Next.js apps — start monitoring for free.

Top comments (0)