In Next.js middleware, what is the difference between returning NextResponse.rewrite(url) and NextResponse.redirect(url)?
answer
- one request or two
- who sees the destination URL
- address bar tells you which
- absolute URL required for both
- rewrite feeds params from the internal path
basics
~20 sNextResponse.rewrite serves a different route internally: the browser keeps the original URL and makes one request. NextResponse.redirect returns a 3xx with a Location header, so the browser makes a second request and the address bar changes.
solid answer
~50 sMiddleware returns a `NextResponse`, and the three verbs are `next()`, `rewrite()` and `redirect()`. A rewrite is server-side only: Next resolves the request against the destination path instead of the requested one, but the response still comes back for the original URL, so the address bar, the shared link and the browser history never mention the destination. A redirect is a real HTTP response — a 3xx with `Location` — so the client makes a second request and ends up on a genuinely different URL. Both need an absolute URL, which is why the idiom is `new URL('/somewhere', request.url)`. `NextResponse.redirect` defaults to 307 and takes a status as its second argument. Rule of thumb: rewrite when the destination is an implementation detail you want hidden, redirect when the canonical location really changed and you want clients, caches and crawlers to learn it.
go deeper
Be able to say plainly that a rewrite keeps the URL and costs one request, while a redirect changes the URL and costs two, and remember that both take an absolute URL built from the incoming request.
Explain what actually happens on the wire for each — a 3xx with Location versus internal route resolution — and that after a rewrite the destination path is what supplies the page's params.
Show judgment about which one a feature deserves: hidden internal mapping versus a canonical move that clients, caches and crawlers should learn, and account for the extra round trip a redirect adds on every matched request.
Own the URL space as a contract. Decide which paths are canonical and stable, which are rewrite targets that may be reshuffled freely, and set the rule that concealment via rewrite never substitutes for authorisation in the route.
## What middleware returns Next.js middleware is a single `middleware.ts` at the project (or `src/`) root that exports a `middleware` function. It runs on the server before the request is matched to a route, and its whole vocabulary is the `NextResponse` it returns: - `NextResponse.next()` — carry on to the route that would normally handle this request. - `NextResponse.rewrite(url)` — carry on, but handle a *different* path. - `NextResponse.redirect(url)` — do not handle it here at all; tell the browser to go somewhere else. Everything else you do in middleware (attaching headers, setting cookies) hangs off one of those three objects. ## Redirect: the client is told to move `NextResponse.redirect(new URL('/pricing', request.url))` produces an actual HTTP response with a redirect status and a `Location` header. The browser then issues a *second* request to that location. Consequences: - The address bar changes, and the new URL is what the user bookmarks, shares and sees in history. - Two network round trips instead of one. - The destination is public knowledge — a crawler, a CDN log and the user all see it. The status defaults to 307. You can pass another one as the second argument: ```ts return NextResponse.redirect(new URL('/pricing', request.url), 308) ``` ## Rewrite: the server quietly serves something else `NextResponse.rewrite(new URL('/tenants/acme/dashboard', request.url))` does not produce a redirect at all. The same in-flight request continues, and Next resolves the *destination* path against your `app` directory. The response body comes back under the URL the browser asked for. - One round trip. - The address bar never changes; the destination path is invisible to the client. - The destination path is what the router matches, so dynamic segments in the destination populate `params` for the page that renders. That last point is the one people miss. If you rewrite `/dashboard` to `/tenants/acme/dashboard` and the destination is `app/tenants/[tenant]/dashboard/page.tsx`, that page receives `params` with `tenant` set to `acme` — the internal path is what feeds params, not the URL the user typed. ## Absolute URLs are required Both helpers take a URL, not a bare path string. `NextResponse.redirect('/pricing')` is the single most common beginner error. Construct it against the incoming request so protocol and host come along: ```ts const url = request.nextUrl.clone() url.pathname = '/pricing' return NextResponse.redirect(url) ``` `request.nextUrl` is a parsed URL object on `NextRequest`; cloning it and editing `pathname` or `searchParams` is the tidy version of the `new URL(path, request.url)` idiom. ## Choosing between them Use a **rewrite** when the mapping is internal plumbing that the user should never see: serving a per-tenant subtree from one hostname, bucketing a request into an A/B variant, prefixing a locale, keeping an old public path alive over a reorganised route tree. The public URL stays canonical, which is exactly what you want when many URLs should *not* multiply. Use a **redirect** when the canonical location genuinely moved, or when you want the client's own state to change — the address bar, history and any link the user copies afterwards. A redirect is self-healing: once clients and crawlers follow it, they learn the new address. A rewrite teaches nobody anything, forever. ## Costs and gotchas A redirect costs a round trip, which matters most on high-latency connections and on paths that every request touches. A rewrite costs clarity: when a support ticket says "/dashboard renders the wrong thing", nothing in the URL hints that a rewrite chose the route, so log the decision. A rewrite is also **not** an access control. The destination path usually remains directly reachable — if `/tenants/acme/dashboard` is a real route, someone can request it. Hiding a path is not protecting it; the route or the data layer still has to check. Finally, both are decisions made *before* the route runs, on the server, once per request. They are not client-side navigation, and the client router is not involved in the choice.
- What status does NextResponse.redirect use if you don't pass one, and how would you send a permanent redirect instead?It defaults to 307. Pass the status as the second argument — `NextResponse.redirect(url, 308)` — when you want a permanent one. The declarative equivalent is a `redirects()` entry in `next.config` with `permanent: true`, which Next emits as 308 (and `permanent: false` as 307).
- After a rewrite, where do the params for the rendered page come from?From the destination path, not the requested one. Next resolves the rewritten path against the `app` directory, so dynamic segments in the destination populate `params`. The URL the browser asked for contributes only its search params, which the rewrite carries over unless you change them.
- Is using a rewrite to hide an internal route a security measure?No. A rewrite only changes which route Next resolves for that request; the destination route is still a real route and usually still directly requestable. Concealment is not authorisation — the route handler, Server Component or data layer must do the actual check regardless of how the request arrived.
saying these in an interview costs you the question
- Thinks a rewrite changes the browser's address bar
- Passes a bare path string instead of an absolute URL
- Believes a rewrite hides the destination route from direct requests
- Says a redirect is faster because it is 'server-side'
- Assumes params come from the visible URL after a rewrite