NextResponse
Extends the Web Response with next(), redirect(), rewrite(), json() and a cookies helper.
class NextResponse extends ResponseParameters
| Parameter | Type | Description |
|---|---|---|
$NextResponse.json(body, init?) | static | Produce a response with the given JSON body. init takes the usual ResponseInit fields such as status and headers. |
$NextResponse.redirect(url, init?) | static | Produce a response that redirects to a URL. The URL can be built and modified first, typically from request.nextUrl. |
$NextResponse.rewrite(url, init?) | static | Produce a response that rewrites (proxies) the given URL while the browser keeps showing the original one. |
$NextResponse.next(init?)optional | static | Return early and continue routing. Pass { request: { headers } } to forward modified request headers upstream. |
$cookies | ResponseCookies | Read or mutate the response's Set-Cookie header: set(name, value), get(name), getAll(name?), has(name), delete(name). |
Return value
A Response you return from proxy() or a Route Handler, carrying Next.js routing semantics.
NextResponse is the Web Response with the four routing decisions a proxy can make bolted on: continue (next()), send the browser elsewhere (redirect()), serve a different route under the same URL (rewrite()), or reply directly (json()). Its cookies jar is where you actually set cookies in the browser from a proxy.
Choosing between them
redirect() changes the address bar and costs a round trip; rewrite() does not and is what you want for A/B splits, locale routing or device variants. next() is the pass-through — return it whenever the proxy has nothing to change, and attach cookies or headers to it first if you do.
What trips people up
NextResponse.next({ request: { headers } })forwards headers upstream to the page or handler and hides them from the client.NextResponse.next({ headers })is different — it sends them to the browser, and overriding things likeContent-Typethat way breaks Server Actions and streaming.- Do not copy all incoming headers forward. Build an allow-list; blanket copying leaks
cookieandauthorizationto upstream services. redirect()needs an absolute URL — construct it withnew URL(path, request.url).
Example
import { type NextRequest, NextResponse } from 'next/server'
export function proxy(request: NextRequest) {
const session = request.cookies.get('session-id')
if (!session) {
const loginUrl = new URL('/login', request.url)
loginUrl.searchParams.set('from', request.nextUrl.pathname)
return NextResponse.redirect(loginUrl)
}
const response = NextResponse.next()
response.cookies.set('last-seen-path', request.nextUrl.pathname, {
path: '/',
sameSite: 'lax',
})
return response
}When to use it
- Redirect unauthenticated users to a login page and remember where they came from
- Rewrite an A/B test variant without changing the visible URL
- Set or clear a cookie from proxy.js while letting the request continue
- Return a JSON error with an explicit status code from a Route Handler
Frequently asked questions
How do I redirect from middleware or proxy.js?
Return NextResponse.redirect() with an absolute URL, built as new URL('/login', request.url). Relative strings are rejected. Use rewrite() instead if you want to serve different content without changing what the address bar shows.
What is the difference between rewrite and redirect?
redirect() sends the browser a redirect response so the URL visibly changes and a second request follows, while rewrite() proxies a different route on the server with the original URL still shown. Rewrites suit A/B tests, locale routing and device variants.
How do I pass data from proxy.js to my page?
Use NextResponse.next({ request: { headers } }) with a modified Headers object; the page reads it through await headers() and the client never sees it. Avoid copying every incoming header forward — build an allow-list, since blanket copying can leak cookie and authorization headers.
More examples
Forwarding a header upstream, safely
The page or Route Handler downstream can now read x-version through await headers(), and the client never sees it. The nesting matters: { request: { headers } } goes upstream, whereas { headers } at the top level sends them to the browser — a shorthand the docs explicitly warn against, since overriding Content-Type there breaks Server Actions and streaming.
import { type NextRequest, NextResponse } from 'next/server'
export function proxy(request: NextRequest) {
// Given an incoming request...
const newHeaders = new Headers(request.headers)
// Add a new header
newHeaders.set('x-version', '123')
// Forward the modified request headers upstream
return NextResponse.next({
request: {
headers: newHeaders,
},
})
}Rewriting a variant behind one URL
Both cohorts share one public URL while Next.js renders two different routes, so each variant keeps its own cache entry and its own build output. A redirect() here would flash a second URL in the address bar and reveal the experiment to the visitor.
import { type NextRequest, NextResponse } from 'next/server'
export function proxy(request: NextRequest) {
const bucket = request.cookies.get('bucket')?.value
if (bucket === 'variant') {
// Incoming request: /pricing, browser shows /pricing
// Rewritten request: /pricing-variant, browser shows /pricing
return NextResponse.rewrite(new URL('/pricing-variant', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: '/pricing',
}JSON responses with explicit status
json() sets Content-Type and serialises the body for you; the second argument is a normal ResponseInit, which is how the 400 is applied. Setting the cookie on the returned response rather than on the request is what actually emits a Set-Cookie header to the browser.
import { NextResponse } from 'next/server'
export async function POST(request: Request) {
const body = await request.json().catch(() => null)
if (!body?.email) {
return NextResponse.json(
{ error: 'email is required' },
{ status: 400 }
)
}
const response = NextResponse.json({ ok: true })
response.cookies.set('signed-up', '1', { path: '/', httpOnly: true })
return response
}
Discussion