Type something to search...
Next.js NextRequest

Next.js NextRequest

Every Route Handler and every proxy.js file in a Next.js application receives a request object, and that object is not a plain Web Request. It's a NextRequest — a thin, purpose-built extension that adds exactly the conveniences you need when you're working inside Next.js's routing layer: structured cookie access and a parsed, framework-aware URL. The rest of the Web Request API — method, headers, body, json(), formData() — is still there, untouched. NextRequest doesn't replace anything; it just saves you from re-implementing cookie parsing and URL parsing by hand in every handler you write.

If you've spent time with Express or a raw Node.js server, this will feel familiar in spirit — those frameworks also wrap the primitive request object with convenience accessors. What's different here is that NextRequest is built directly on top of the standard Request class from the Fetch API, which means anything you already know about handling web requests transfers directly, and anything a library expects from a standard Request still works when you pass a NextRequest to it.

Where You Actually Encounter NextRequest

You don't construct a NextRequest yourself in normal usage — Next.js hands you one. There are exactly two places this happens:

Route Handlers, where the first parameter to an exported HTTP method function is typed as NextRequest:

// app/api/users/route.ts
import { NextRequest, NextResponse } from "next/server";

export async function GET(request: NextRequest) {
  const name = request.nextUrl.searchParams.get("name");
  return NextResponse.json({ message: `Hello, ${name ?? "stranger"}` });
}

proxy.js (the file that replaced Middleware in recent Next.js versions), where the exported function also receives a NextRequest:

// proxy.ts
import { NextRequest, NextResponse } from "next/server";

export function proxy(request: NextRequest) {
  const token = request.cookies.get("session")?.value;
  if (!token) {
    return NextResponse.redirect(new URL("/login", request.url));
  }
  return NextResponse.next();
}

Server Components, Server Actions, and page.js/layout.js files never receive a NextRequest directly — for those, you read cookies and headers through the dedicated cookies() and headers() functions instead. NextRequest is specifically the request-handling-layer type, not a general-purpose "current request" object you can reach for anywhere.

The cookies Property

The single most useful thing NextRequest adds over a plain Request is request.cookies — a RequestCookies instance that gives you a Map-like API over the raw Cookie header, instead of making you parse a semicolon-delimited string yourself.

get(name)

Returns the first cookie matching that name, or undefined if it isn't present:

const theme = request.cookies.get("theme");
// { name: 'theme', value: 'dark' } | undefined
console.log(theme?.value);

Note that this returns an object with a name and value, not the raw string — a detail that trips people up the first time. You almost always want .value off the result, not the object itself.

getAll(name?)

Two distinct behaviors depending on whether you pass an argument. With a name, it returns every cookie matching that name (useful when the same cookie key legitimately appears more than once, which can happen with cookies scoped to different paths):

const experiments = request.cookies.getAll("experiment");
// [{ name: 'experiment', value: 'variant-a' }, { name: 'experiment', value: 'variant-b' }]

Called with no argument, it returns every cookie on the request:

const all = request.cookies.getAll();

has(name)

A boolean existence check, useful for guard clauses where you don't actually need the value:

if (!request.cookies.has("session")) {
  return NextResponse.redirect(new URL("/login", request.url));
}

set(name, value)

This one deserves a callout because it's easy to reach for it expecting it to behave like response.cookies.set() — it doesn't send anything back to the browser. Setting a cookie on the request mutates the Cookie header that gets forwarded onward, which is useful in proxy.js when you want a downstream Route Handler or a rewritten destination to see a cookie value that wasn't actually present in the original request:

export function proxy(request: NextRequest) {
  request.cookies.set("show-banner", "false");
  return NextResponse.next({ request });
}

If your goal is to make the browser store a cookie, you need response.cookies.set() on the NextResponse you return, not request.cookies.set(). Confusing the two is one of the most common NextRequest/NextResponse mistakes, precisely because the method names and signatures are identical on both objects.

delete(name) and clear()

delete(name) removes a single cookie from the request and returns a boolean indicating whether anything was actually removed. clear() wipes every cookie on the request in one call. Both are request-scoped mutations with the same "affects what gets forwarded, not what the browser holds" caveat as set().

The nextUrl Property

request.url still exists and still gives you the full URL as a plain string, exactly as it would on a standard Request. request.nextUrl sits alongside it as a parsed, structured alternative — effectively a URL object with a few Next.js-specific fields layered in.

export function proxy(request: NextRequest) {
  const { pathname, searchParams, basePath } = request.nextUrl;

  if (pathname.startsWith("/admin")) {
    // ...
  }
}

The properties worth knowing:

PropertyWhat it gives you
pathnameThe path portion of the URL, already decoded — no manual new URL(request.url).pathname needed
searchParamsA URLSearchParams instance, same as new URL(...).searchParams would give you
basePathThe app's configured basePath from next.config.js, if one is set
buildIdThe current build's identifier, if you've customized generateBuildId

In practice, pathname and searchParams are the two you'll use constantly, and basePath/buildId are edge-case tools for multi-zone setups or deployment tooling that needs to know which build served a request.

One thing the docs are explicit about: the Pages Router's internationalization properties on the request (locale, locales, defaultLocale) do not carry over to nextUrl in the App Router. If you're coming from a Pages Router codebase and reaching for request.nextUrl.locale, it won't be there — App Router internationalization is handled through routing conventions instead, not request properties.

What Got Removed: ip and geo

If you're working from slightly older Next.js knowledge, you may expect request.ip and request.geo to exist. They were removed in v15.0.0 and do not exist on NextRequest in current versions. This trips up a lot of people upgrading from Next.js 14, because those two properties used to be a one-line way to get a visitor's IP address and rough location.

The replacement path is platform-specific: on Vercel, the equivalent data now comes from the @vercel/functions package's geolocation() and ipAddress() helpers, which read the same underlying request headers Vercel's edge network sets. If you're self-hosting, you'd read the relevant forwarded-for headers directly:

import { geolocation, ipAddress } from "@vercel/functions";

export function proxy(request: NextRequest) {
  const { city, country } = geolocation(request);
  const ip = ipAddress(request);
  // ...
}

The reasoning behind the removal is that IP-based geolocation is fundamentally an infrastructure concern, not a framework concern — different hosts populate this information differently (or not at all), so baking it into NextRequest gave people a false sense that it would work identically everywhere. Decoupling it into a platform package makes that dependency explicit instead of implicit.

NextRequest vs. the Plain Web Request

It's worth being precise about the relationship here, because "extends" gets used loosely. NextRequest genuinely extends the global Request class in the class-inheritance sense — every method and property a standard Request has (method, headers, body, bodyUsed, json(), text(), formData(), arrayBuffer(), clone()) is present and behaves identically. cookies and nextUrl are additions, not replacements.

This has a practical consequence: any code written against the standard Request type — a validation library, a body parser, a piece of shared middleware logic you also use outside Next.js — works unmodified when you hand it a NextRequest. You're never boxed into a Next.js-only request shape; you're handed a strict superset.

// This works because NextRequest IS a Request
async function parseJsonBody(request: Request) {
  return request.json();
}

export async function POST(request: NextRequest) {
  const body = await parseJsonBody(request); // fine — NextRequest satisfies Request
  // ...
}

Common Mistakes

Reading request.cookies.get(name) and using it directly as a string. It returns { name, value }, not the raw value. You need .value.

Expecting request.cookies.set() to set a cookie in the browser. It only affects the outgoing request as it continues through your app (relevant in proxy.js when forwarding to a Route Handler). To make the browser store a cookie, set it on the NextResponse instead.

Reaching for request.ip or request.geo. Both were removed in v15. Use your hosting platform's dedicated helpers, or read the forwarded headers directly if self-hosting.

Using request.url when you actually want request.nextUrl.pathname. request.url is a full string URL and requires you to parse it yourself if you only want the path — nextUrl.pathname is already parsed and is almost always the more convenient choice inside a handler.

Assuming nextUrl carries Pages Router locale data. It doesn't. App Router internationalization is a routing-convention concern, not a request-property concern.

Key Takeaways

FeatureWhat it does
request.cookies.get/getAll/has/set/delete/clearStructured read/write access to the request's Cookie header
request.nextUrl.pathname / .searchParamsPre-parsed URL pieces, no manual new URL() needed
request.nextUrl.basePath / .buildIdDeployment-level metadata, mostly relevant to multi-zone setups
request.ip / request.geoRemoved in v15 — use your host's geolocation helpers instead
Everything else (method, headers, json(), etc.)Identical to the standard Web Request API

NextRequest is deliberately a small surface area: two additions on top of a class you already understand if you've worked with the Fetch API at all. The value isn't in a large API to learn — it's in not having to hand-roll cookie parsing and URL parsing in every Route Handler and every proxy.js file you write. Once you internalize that cookies and nextUrl are the only real additions, and that the removed ip/geo properties now live in platform-specific packages, there's very little left to trip over.

Tags :
Share :

Related Posts

Can Next.js Be Used with GraphQL?

Can Next.js Be Used with GraphQL?

Next.js and GraphQL are two powerful technologies that have gained significant traction in the web development community. Next.js, a React-based fram

Dive Deeper
How does Next.js differ from Create React App?

How does Next.js differ from Create React App?

In the world of modern web development, React.js has emerged as a dominant force due to its flexibility, performance, and extensive ecosystem. Two po

Dive Deeper
How does Next.js handle image optimization?

How does Next.js handle image optimization?

In modern web development, image optimization plays a critical role in enhancing user experience and improving site performance. Large, unoptimized i

Dive Deeper