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.
NextFetchEvent
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();
event.waitUntil(
(async () => {
const writer = writable.getWriter();
const encoder = new TextEncoder();
writer.write(encoder.encode('Hello, world! Streamed!'));
writer.write(encoder.encode('response'));
writer.close();
})(),
);
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';
NextRequest
The NextRequest
object is an extension of the native Request
interface, with the following added methods and properties:
cookies
- Has the cookies from theRequest
nextUrl
- Includes an extended, parsed, URL object that gives you access to Next.js specific properties such aspathname
,basePath
,trailingSlash
andi18n
geo
- Has the geo location from theRequest
geo.country
- The country codegeo.region
- The region codegeo.city
- The city
ip
- Has the IP address of theRequest
ua
- Has the user agent
geo
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
const BLOCKED_COUNTRY = 'GB';
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.`);
}
NextResponse
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 aNextResponse
with a redirect setrewrite()
- Returns aNextResponse
with a rewrite setnext()
- Returns aNextResponse
that will continue the middleware chainjson()
- 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 theResponse
cookie()
- Set a cookie in theResponse
clearCookie()
- Accepts acookie
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`
response.clearCookie('hello');
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 fromPOST
toGET
307
- Temporary redirect, will preserve the request method asPOST
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:
Globals
Base64
atob
: Decodes a string of data which has been encoded using base-64 encodingbtoa
: Creates a base-64 encoded ASCII string from a string of binary data
Encoding
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
Environment
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, theprocess.env
object will be empty.process.env
will only include environment variables actually used.
Fetch
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.
Streams
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 environmentReadableStream
: A readable stream of byte dataWritableStream
: A standard abstraction for writing streaming data to a destination, known as a sink
Timers
setInterval
: Schedules a function to execute every time a given number of milliseconds elapsesclearInterval
: Cancels the repeated execution set usingsetInterval()
setTimeout
: Schedules a function to execute in a given amount of timeclearTimeout
: Cancels the delayed execution set usingsetTimeout()
Web
Headers
: A WHATWG implementation of the headers APIURL
: A WHATWG implementation of the URL API.URLSearchParams
: A WHATWG implementation ofURLSearchParams
AbortController
: Abort one or more Web requests when desired.AbortSignal
: Communicate with a DOM request and abort if needed via anAbortController
object.
Crypto
Crypto
: TheCrypto
interface represents basic cryptography features available in the current contextcrypto.randomUUID
: Lets you generate a v4 UUID using a cryptographically secure random number generatorcrypto.getRandomValues
: Lets you get cryptographically strong random valuescrypto.subtle
: A read-only property that returns a SubtleCrypto which can then be used to perform low-level cryptographic operations
Logging
console.debug
: Outputs a message to the console with the log level debugconsole.info
: Informative logging of information. You may use string substitution and additional arguments with this methodconsole.clear
: Clears the consoleconsole.dir
: Displays an interactive listing of the properties of a specified JavaScript objectconsole.count
: Log the number of times this line has been called with the given labelconsole.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 stringnew Function(evalString)
: Creates a new function with the code provided as an argument