Ahmad Awais



Vercel Edge Functions with Next.js

Ahmad AwaisAhmad Awais

Vercel’s Edge Functions to be used with Next.js (v12) announced today at the second Next.js Conference look pretty amazing. Now everyone building on Next.js using Vercel will have access to a super useful middleware.

I’ll be deleting a bunch of infrastructure code and probably thousands of lines of code that I have now for things like basic auth and purchasing power parity. This is pretty amazing.

Edge Functions#

Traditionally there are two ways to serve content, statically from a Content Delivery Network (CDN) close to the user for fast response times, or dynamically, with personalization configured at the server level on each request.

When deciding on how you want to deliver content to your application visitors, you have to take into consideration the trade-offs that each of these two options offer.

A static page will deliver the same content to all visitors, no matter where they are in the world, and it will be fast as it’s cached by the CDN. But this approach may not be viable if you want to deliver personalized content, depending on, for example, where a user is located in the world.

To give your user a personalized experience, you can take advantage of server-side rendering to create dynamic content on each request to your sites pages. This will enable you to offer different content to people based on their location, authenticate them, or configure the language of your site.

The drawback of this approach is that it can be slower. If the server processing the request is far away from the visitors origin, then the request can take time to complete, and the content may not be available to the user at the speed offered by serving purely static content.

What are Edge Functions?#

To achieve both speed and dynamism, you can use Edge Functions. They allow you to deliver content to your sites visitors with speed and personalization, are deployed globally by default on Vercel’s Edge Network, and have zero cold starts. They enable you to move server-side logic to the Edge, close to your visitors origin.

To use Edge Functions, you can deploy Middleware. Middleware is code that executes before a request is processed. Depending on the incoming request, you can execute custom logic, rewrite, redirect, add headers and more, before returning a response.

Middleware (Vercel & Nextjs)#

The middleware function runs code before a request is completed, then based on the request, you can modify the response. It can be used for anything that shares logic between pages.

It takes two parameters, request, and event. The request parameter is an extension of the native Request interface and has added methods and properties that include accessing cookies, getting geolocation from an IP Address, and user-agent info. You can import its type definition with

In addition, you can import the NextResponse API, which extends the native Response interface and lets you redirect, rewrite, cookies, and clear cookies.

Middleware use-cases#

How to use Middleware

To start using Middleware in your Next.js project, begin by upgrading to the latest Next.js version. The following steps will guide you through the process. Note that the below example uses TypeScript, though this is not a requirement.

  1. Install the latest version of next:

    npm install [email protected] 
    # or 
    yarn upgrade [email protected]
  2. Next, create a _middleware.ts file under your /pages directory.

    - /pages
    - package.json
  3. Finally, create function in the _middleware.ts file.

    export default function middleware(req, ev) {
      return new Response({
        body: 'Hello, world!',

When you deploy your site, your Middleware will work out of the box


Middleware is created by using a middleware function that lives inside a _middleware file. Its API is based upon the native FetchEvent, Response, and Request objects.

These native Web API objects are extended to give you more control over how you manipulate and configure a response, based on the incoming requests.

The function signature:

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

export type Middleware = (
  request: NextRequest,
  event: NextFetchEvent,
) => Promise<Response | undefined> | Response | undefined;

The function can be a default export and as such, does not have to be named middleware. Though this is a convention. Also, note that you only need to make the function async if you are running asynchronous code.

Warning: Edge Functions are currently in Beta. The API might change as we look to continually make improvements.


The NextFetchEvent object extends the native FetchEvent object, and includes the waitUntil() method.

The waitUntil() method can be used to prolong the execution of the function, after the response has been sent. In practice this means that you can send a response, then continue the function execution if you have other background work to make.

An example of why you would use waitUntil() is integrations with logging tools such as Sentry or DataDog. After the response has been sent, you can send logs of response times, errors, API call durations or overall performance metrics.

The event object is fully typed and can be imported from next/server.

import { NextFetchEvent } from 'next/server';


The NextRequest object is an extension of the native Request interface, with the following added methods and properties:

You can use the NextRequest object as a direct replacement for the native Request interface, giving you more control over how you manipulate the request.

NextRequest is fully typed and can be imported from next/server.

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

Example using the geo object to check a requests location and blocking if it does not match an allowlist:

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

// Block GB, prefer US

export function middleware(req: NextRequest) {
  const country = req.geo.country || 'US';

  // If the request is from the blocked country,
  // send back a response with a status code
  if (country === BLOCKED_COUNTRY) {
    return new Response('Blocked for legal reasons', { status: 451 });

  // Otherwise, send a response with the country
  return new Response(`Greetings from ${country}, where you are not blocked.`);


The NextResponse object is an extension of the native Response interface, with the following added methods and properties:

All methods above return a NextResponse object that only takes effect if it’s returned in the middleware function.

NextResponse is fully typed and can be imported from next/server.

import { NextResponse } from 'next/server';

Example using rewrite() to rewrite the response to a different URL based on the request (browser) location:

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

export function middleware(req: NextRequest) {
  const country = req.geo.country?.toLowerCase() || 'us';

  req.nextUrl.pathname = `/${country}`;
  return NextResponse.rewrite(req.nextUrl);

Why does redirect() use 307 and 308?

When using redirect() you may notice that the status codes used are 307 for a temporary redirect, and 308 for a permanent redirect. While traditionally a 302 was used for a temporary redirect, and a 301 for a permanent redirect, many browsers changed the request method of the redirect, from a POST to GET request when using a 302, regardless of the origins request method.

Taking the following example of a redirect from /users to /people, if you make a POST request to /users to create a new user, and are conforming to a 302 temporary redirect, the request method will be changed from a POST to a GET request. This doesn’t make sense, as to create a new user, you should be making a POST request to /people, and not a GET request.

The introduction of the 307 status code means that the request method is preserved as POST.

The redirect() method uses a 307 by default, instead of a 302 temporary redirect, meaning your requests will always be preserved as POST requests.

Middleware running order

If you do not have any sub-directories, the Middleware will run on all routes within the /pages directory and public files like /favicon.ico. The below example assumes you have about.ts and teams.ts routes.

- /pages
  _middleware.ts # Will run on all routes under /pages
- package.json

If you do have sub-directories with nested routes, the Middleware will run in a top-down fashion. For example, if you have created /pages/about/_middleware.ts and /pages/about/team/_middleware.ts, the Middleware will run first on /pages/about, and then /pages/about/team. The below example shows how this works with a nested routing structure.

- /pages
  - /about
    _middleware.ts # Will run first
    - /teams
      _middleware.ts # Will run second
- package.json


Once the Middleware is deployed, it will run within a V8 Runtime with a limited set of APIs. In development, the code will run in a sandbox environment that emulates the production runtime.

Because of this, there are some restrictions to writing Middleware. These include:

Runtime APIs

The following objects and APIs are available in the runtime:






The Web Fetch API can be used from the runtime, enabling you to use Middleware as a proxy, or connect to external storage APIs

A potential caveat to using the Fetch API in a Middleware function is latency. For example, if you have a Middleware function running a fetch request to New York, and a user accesses your site from London, the request will be resolved from the nearest Edge to the user (in this case, London), to the origin of the request, New York. There is a risk this could happen on every request, making your site slow to respond. When using the Fetch API, you must make sure it does not run on every single request made.






Unsupported APIs

The Edge Runtime has some restrictions including:

The following JavaScript language features are disabled, and will not work:

The following Web APIs are currently not supported, but will be in the future:

Technical Details

Maximum Execution Duration

The maximum duration for an Edge Function execution is 30 seconds, but the function needs to return a response in less than 1.5 seconds, otherwise, the request will time out.

This means that you should return a response as soon as possible, and continue with any asynchronous workloads in the background, after returning the response.

Code size limit

The maximum size for an Edge Function is 1 MB, including all the code that is bundled in the function.

If you reach the limit, make sure the code you are importing in your function is used and is not too heavy. You can use a package size checker tool like a bundle to check the size of a package and search for a smaller alternative.

Helping businesses build developer-led adoption for 10+ yrs.
VP of Developer Relations (DevRel, DX Eng., Dev Content Docs) at RapidAPI. I'm Hiring!

๐Ÿ“Ÿ Edutainer at Node.js CLI Automation course (100 videos ยท 22 Projects) & VSCode.pro course (over 20K devs). ๐ŸŽฉ Award-winning Open Source Engineer & Dev Advocate ๐ŸฆŠ Google Developers Expert Web DevRel ๐Ÿš€ Mars Ingenuity Helicopter code contributor ๐Ÿ† 8th GitHub Stars Award recipient ๐ŸŒณ Node.js foundation Community Committee Outreach Lead, Member Linux Foundation, OpenAPI Business Governing Board, and DigitalOcean Navigator.

โœŒ๏ธ Author of various open-source dev-tools and software libraries used by millions of developers worldwide โ“ฆ WordPress Core Developer ๐Ÿ“ฃ TEDx Speaker with 100+ international talks.

๐Ÿ”ฅ Satya Nadella ยท CEO of Microsoft, said โ€” Awais is an awesome example for developers.
๐Ÿ™Œ Leading developers and publishing technical content for over a decade ๐Ÿ’œ Loves his wife (Maedah) โฏ Read more about Ahmad Awais.

๐Ÿ‘‹โ€ฆ Awais is mostly active on Twitter @MrAhmadAwais


Developers Takeaway

Takeaway my professional opinion on open-source, developer relations, growing your business with family, and everything in between! Delivered to your inbox a couple of times every year. I'm even funny at times. I hate spam โ€” pinky-promise!

๐Ÿ‘จโ€๐Ÿ’ป๐Ÿ‘ฉโ€๐Ÿ’ป 118,947 Developers Already Subscribed
Comments 0
There are currently no comments.