NextRequest
Extends the Web Request with cookie helpers and a nextUrl object for proxy and Route Handlers.
class NextRequest extends RequestParameters
| Parameter | Type | Description |
|---|---|---|
$cookies | RequestCookies | Read or mutate the request's cookies: set(name, value), get(name), getAll(name?), delete(name), has(name) and clear(). |
$nextUrl | NextURL | The request URL extended with Next.js properties. Also carries everything from the native URL API. |
$nextUrl.basePath | string | The basePath of the URL. |
$nextUrl.buildIdoptional | string | undefined | The build identifier of the Next.js application. Can be customized with generateBuildId. |
$nextUrl.pathname | string | The pathname of the URL. |
$nextUrl.searchParams | Object | The search parameters of the URL. |
Return value
An instance handed to you by Next.js as the argument to proxy() and to Route Handler methods.
Changelog
- PHP v15.0.0The ip and geo properties were removed. Read the client address from the x-forwarded-for header, or use your host's own headers, instead.
NextRequest is the ordinary Web Request plus two conveniences: a parsed cookies jar and nextUrl. You do not construct it in application code — Next.js passes it to proxy() and to each Route Handler export, and importing the type is what gives you autocomplete on both.
When to reach for it
Type your proxy.js argument with it whenever you need to inspect the path, read a session cookie before deciding to redirect, or pull a query parameter. nextUrl is the piece worth knowing: it is a URL, so clone(), pathname and searchParams all behave normally, and it is the object you hand to NextResponse.rewrite() or redirect().
What trips people up
request.ipandrequest.geowere removed in v15.0.0. Readx-forwarded-forinstead.- Mutating
request.nextUrlin place affects the object you later pass torewrite(). Callclone()first when you want the original left alone. cookies.get()returns{ name, value }orundefined, not a bare string.- Setting a cookie on the request changes what runs downstream see; to change what the browser stores, set it on the
NextResponse. - The i18n properties from the Pages Router do not exist here.
Example
import { NextRequest, NextResponse } from 'next/server'
export function proxy(request: NextRequest) {
const session = request.cookies.get('session-id')
if (!session && request.nextUrl.pathname.startsWith('/dashboard')) {
const loginUrl = new URL('/login', request.url)
loginUrl.searchParams.set('from', request.nextUrl.pathname)
return NextResponse.redirect(loginUrl)
}
return NextResponse.next()
}
export const config = {
matcher: '/dashboard/:path*',
}When to use it
- Redirect unauthenticated visitors from proxy.js by checking a session cookie
- Read a dynamic segment's query parameters inside a Route Handler
- Clone nextUrl and rewrite to a different pathname while keeping the visible URL
- Forward a request cookie downstream so pages read a value the proxy computed
Frequently asked questions
How do I get the request IP in Next.js 15?
request.ip was removed in v15.0.0, along with request.geo. Read the x-forwarded-for header — request.headers.get('x-forwarded-for') — and take the first entry, or use whatever geolocation headers your hosting platform injects.
What is the difference between request.url and request.nextUrl?
request.url is the plain string from the Web Request API, while nextUrl is a parsed URL object extended with Next.js properties like basePath and buildId. Use nextUrl for pathname and searchParams, and clone() it before mutating.
Why doesn't the cookie I set on the request reach the browser?
Because request.cookies.set() only changes what downstream pages and handlers see during this request. To store a cookie in the browser, set it on the response — response.cookies.set(...) on a NextResponse, or cookies().set() in a Server Function or Route Handler.
More examples
Reading query parameters in a Route Handler
nextUrl.searchParams is a standard URLSearchParams, so get() returns a string or null and every value needs coercing yourself. Typing the parameter as NextRequest rather than Request is what makes nextUrl visible — a plain Request only offers request.url, which you would have to parse by hand.
import { NextRequest } from 'next/server'
export function GET(request: NextRequest) {
const query = request.nextUrl.searchParams.get('q') ?? ''
const page = Number(request.nextUrl.searchParams.get('page') ?? '1')
return Response.json({ query, page })
}The full cookies API on a request
Every mutation here rewrites the request as the rest of your application will see it — a page calling cookies() downstream reads 'false' for show-banner. Nothing reaches the browser: for that you must set the cookie on the NextResponse you return, which is the distinction behind most 'my cookie isn't sticking' reports.
import { NextRequest, NextResponse } from 'next/server'
export function proxy(request: NextRequest) {
request.cookies.has('experiments') // boolean
request.cookies.get('show-banner') // { name, value, ... } | undefined
request.cookies.getAll('experiments') // array of matching cookies
request.cookies.delete('experiments') // true if one was removed
// Visible to pages and handlers downstream, not to the browser
request.cookies.set('show-banner', 'false')
return NextResponse.next()
}Rewriting with a cloned URL
clone() gives a detached copy, so editing pathname and searchParams cannot disturb the live request object that later handlers inspect. The browser keeps showing /legacy because this is a rewrite rather than a redirect — the URL change happens only on the server side.
import { NextRequest, NextResponse } from 'next/server'
export function proxy(request: NextRequest) {
const url = request.nextUrl.clone()
if (url.pathname === '/legacy') {
url.pathname = '/new'
url.searchParams.set('migrated', '1')
return NextResponse.rewrite(url)
}
return NextResponse.next()
}
Discussion