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

The Middleware 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, 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.


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.

The following example creates a stream and sends the response async:

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

export async function middleware(req: NextRequest, event: NextFetchEvent) {
  if (req.nextUrl.pathname === '/responses/send-response') {
    const { readable, writable } = new TransformStream();

      (async () => {
        const writer = writable.getWriter();
        const encoder = new TextEncoder();
        writer.write(encoder.encode('Hello, world! Streamed!'));

    return new Response(readable);

Another 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 type { NextFetchEvent } from 'next/server';


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

  • cookies - Has the cookies from the Request
  • nextUrl - Includes an extended, parsed, URL object that gives you access to Next.js specific properties such as pathname, basePath, trailingSlash and i18n
  • geo - Has the geo location from the Request
    • geo.country - The country code
    • geo.region - The region code
    • geo.city - The city
  • ip - Has the IP address of the Request
  • ua - Has the user agent
Note: When working locally, your IP address will be This means that thegeo location can't be computed and you will see an empty object. To check the geo objects properties, push your code and Vercel will create a deployment with a Preview URL, this URL can be used to check the geo location values from the Request.

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 class extends the native Response interface, with the following:

Static methods

The following static methods are available on the NextResponse class directly:

  • redirect() - Returns a NextResponse with a redirect set
  • rewrite() - Returns a NextResponse with a rewrite set
  • next() - Returns a NextResponse that will continue the middleware chain
  • json() - A convenience method to create a response that encodes the provided JSON data
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // if the request is coming from New York, redirect to the home page
  if (request.geo.city === 'New York') {
    return NextResponse.redirect('/home');
    // if the request is coming from London, rewrite to a special page
  } else if (request.geo.city === 'London') {
    return NextResponse.rewrite('/not-home');

  return NextResponse.json({ message: 'Hello World!' });

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);

Public methods

Public methods are available on an instance of the NextResponse class. Depending on your use case, you can create an instance and assign to a variable, then access the following public methods:

  • cookies - An object with the cookies in the Response
  • cookie() - Set a cookie in the Response
  • clearCookie() - Accepts a cookie and clears it
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // create an instance of the class to access the public methods. This uses `next()`,
  // you could use `redirect()` or `rewrite()` as well
  let response = NextResponse.next();
  // get the cookies from the request
  let cookieFromRequest = request.cookies['my-cookie'];
  // set the `cookie`
  response.cookie('hello', 'world');
  // set the `cookie` with options
  const cookieWithOptions = response.cookie('hello', 'world', {
    path: '/',
    maxAge: 1000 * 60 * 60 * 24 * 7,
    httpOnly: true,
    sameSite: 'strict',
    domain: 'example.com',
  // clear the `cookie`

  return response;

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.

  • 302 - Temporary redirect, will change the request method from POST to GET
  • 307 - Temporary redirect, will preserve the request method 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.

Runtime APIs

The following objects and APIs are available in the runtime:



  • atob: Decodes a string of data which has been encoded using base-64 encoding
  • btoa: Creates a base-64 encoded ASCII string from a string of binary data


  • TextEncoder: Takes a stream of code points as input and emits a stream of bytes (UTF8)
  • TextDecoder: Takes a stream of bytes as input and emit a stream of code points


  • process.env: Holds an object with all environment variables used in Middleware, for both production and development environments. If you are not using any environment variables in your Middleware, the process.env object will be empty. process.env will only include environment variables actually used.


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. 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.


  • TransformStream: Consists of a pair of streams: a writable stream known as its writable side, and a readable stream, known as its readable side. Writes to the writable side, result in new data being made available for reading from the readable side. Support for web streams is quite limited at the moment, although it is more extended in the development environment
  • ReadableStream: A readable stream of byte data
  • WritableStream: A standard abstraction for writing streaming data to a destination, known as a sink


  • setInterval: Schedules a function to execute every time a given number of milliseconds elapses
  • clearInterval: Cancels the repeated execution set using setInterval()
  • setTimeout: Schedules a function to execute in a given amount of time
  • clearTimeout: Cancels the delayed execution set using setTimeout()


  • Headers: A WHATWG implementation of the headers API
  • URL: A WHATWG implementation of the URL API.
  • URLSearchParams: A WHATWG implementation of URLSearchParams
  • AbortController: Abort one or more Web requests when desired.
  • AbortSignal: Communicate with a DOM request and abort if needed via an AbortController object.


  • Crypto: The Crypto interface represents basic cryptography features available in the current context
  • crypto.randomUUID: Lets you generate a v4 UUID using a cryptographically secure random number generator
  • crypto.getRandomValues: Lets you get cryptographically strong random values
  • crypto.subtle: A read-only property that returns a SubtleCrypto which can then be used to perform low-level cryptographic operations


  • console.debug: Outputs a message to the console with the log level debug
  • console.info: Informative logging of information. You may use string substitution and additional arguments with this method
  • console.clear: Clears the console
  • console.dir: Displays an interactive listing of the properties of a specified JavaScript object
  • console.count: Log the number of times this line has been called with the given label
  • console.time: Starts a timer with a name specified as an input parameter

Unsupported APIs and Runtime restrictions

The Edge Runtime has some restrictions including:

  • Native Node.js APIs are not supported. For example, you can't read or write to the filesystem
  • Node Modules can be used, as long as they implement ES Modules and do not use any native Node.js APIs. For example, you could use the path-to-regexp package to do path matches
  • You can use ES Modules and split your code into reusable files that will then be bundled together when the application is built
  • Calling require directly is not allowed. If you do use it, it might work when the import path can be statically resolved, but it is not recommended. Use ES Modules instead

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

  • eval: Evaluates JavaScript code represented as a string
  • new Function(evalString): Creates a new function with the code provided as an argument