nextjs - 💡(How to fix) Fix basePath stripped from Location header in validateRSCRequestHeaders 307 [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#93435Fetched 2026-05-04 04:57:47
View on GitHub
Comments
1
Participants
2
Timeline
7
Reactions
0
Author
Timeline (top)
labeled ×2closed ×1commented ×1locked ×1

Root Cause

Hit in production on an app deployed under a non-root basePath and sharing a hostname with another backend (a marketing site mounted at /, with the Next app at /app/*). After a router.push('/api/cookie?…') whose route handler 307s to /app/subscribe?… (without forwarding _rsc=), the browser ended up outside the basePath and landed on the wrong backend (/subscribe → 404 from the other service). Diagnosis took several hours because the Location header is generated several layers below where most users instrument logs — the defense fires silently before page handlers run.

Fix Action

Fix

Two-line patch — re-prepend nextConfig.basePath to the Location header. Originally introduced in #80669, neither that PR nor #80157 (which gated the flag) accounted for basePath.

Pull request: vercel/next.js#93434 — includes a regression test under test/e2e/app-dir/segment-cache/cdn-cache-busting-base-path/.

cc the original authors of the relevant area: @acdlite (#80157, surrounding code) — happy to make any requested changes.

Code Example

Operating System:
  Platform: linux
  Arch: x64
  Version: #1 SMP Debian 5.10.0
Binaries:
  Node: 20.x
  npm: N/A
  Yarn: N/A
  pnpm: 10.x
Relevant Packages:
  next: 16.3.0-canary.9 (also reproduced on 16.1.1-canary.29)
  react: 19.x
  react-dom: 19.x
Next.js config:
  basePath: '/app'
  output: 'standalone'

---

// packages/next/src/server/base-server.ts
const url = new URL(req.url || '', 'http://localhost')
setCacheBustingSearchParamWithHash(url, expectedHash)
res.statusCode = 307
res.setHeader('location', `${url.pathname}${url.search}`)

---

// base-server.ts ~line 1055
if (pathnameInfo.basePath) {
  req.url = removePathPrefix(req.url!, this.nextConfig.basePath)
}

---

# Minimal repro:
curl -sI -H 'rsc: 1' \
  -H 'next-router-prefetch: 1' \
  -H 'next-router-segment-prefetch: /_tree' \
  http://localhost:3000/app/some-page

# Expected: HTTP/1.1 307
#           Location: /app/some-page?_rsc=<hash>
# Actual:   HTTP/1.1 307
#           Location: /some-page?_rsc=<hash>
RAW_BUFFERClick to expand / collapse

Verify canary release

  • I verified that the issue exists in the latest Next.js canary release

Provide environment information

Operating System:
  Platform: linux
  Arch: x64
  Version: #1 SMP Debian 5.10.0
Binaries:
  Node: 20.x
  npm: N/A
  Yarn: N/A
  pnpm: 10.x
Relevant Packages:
  next: 16.3.0-canary.9 (also reproduced on 16.1.1-canary.29)
  react: 19.x
  react-dom: 19.x
Next.js config:
  basePath: '/app'
  output: 'standalone'

Which area(s) are affected? (Select all that apply)

App Router, Caching, Middleware

Which stage(s) are affected? (Select all that apply)

next start (local), Other (Deployed)

Additional context

When experimental.validateRSCRequestHeaders is enabled (the default on canary builds — config-shared.ts defaults to !!(process.env.__NEXT_TEST_MODE || !isStableBuild())), Server.renderToResponseWithComponentsImpl issues a 307 redirect whenever the client-supplied _rsc cache-busting search param does not match the hash computed from the RSC request headers. This is the cache-poisoning defense added in #80669 and gated behind the flag in #80157.

The redirect's Location header is built from req.url:

// packages/next/src/server/base-server.ts
const url = new URL(req.url || '', 'http://localhost')
setCacheBustingSearchParamWithHash(url, expectedHash)
res.statusCode = 307
res.setHeader('location', `${url.pathname}${url.search}`)

But by the time this code runs, basePath has already been stripped from req.url earlier in handleRequestImpl:

// base-server.ts ~line 1055
if (pathnameInfo.basePath) {
  req.url = removePathPrefix(req.url!, this.nextConfig.basePath)
}

So for any app configured with basePath: '/app' (or any non-root basePath), the redirect's Location is /<page>?_rsc=… instead of /app/<page>?_rsc=…. The browser then navigates outside the app's basePath.

Reproduction steps

  1. App with basePath: '/app' and validateRSCRequestHeaders: true (or canary defaults).
  2. Issue an RSC request to /app/<page> with valid RSC headers but no _rsc= query param. (This is what the browser naturally does when following a same-origin redirect chain that lands on an in-app page without a _rsc= query — for example, a route handler that 307s to an app-router page without forwarding _rsc.)
  3. Server emits a 307 with Location: /<page>?_rsc=<expectedHash>/app is missing.
  4. Browser navigates outside the app.
# Minimal repro:
curl -sI -H 'rsc: 1' \
  -H 'next-router-prefetch: 1' \
  -H 'next-router-segment-prefetch: /_tree' \
  http://localhost:3000/app/some-page

# Expected: HTTP/1.1 307
#           Location: /app/some-page?_rsc=<hash>
# Actual:   HTTP/1.1 307
#           Location: /some-page?_rsc=<hash>

Real-world impact

Hit in production on an app deployed under a non-root basePath and sharing a hostname with another backend (a marketing site mounted at /, with the Next app at /app/*). After a router.push('/api/cookie?…') whose route handler 307s to /app/subscribe?… (without forwarding _rsc=), the browser ended up outside the basePath and landed on the wrong backend (/subscribe → 404 from the other service). Diagnosis took several hours because the Location header is generated several layers below where most users instrument logs — the defense fires silently before page handlers run.

The same flow will affect any deployment that:

  • Uses a non-root basePath,
  • Is on canary (where validateRSCRequestHeaders is on by default), and
  • Has any redirect chain that lands on an app-router page through an RSC navigation without an explicit _rsc= on the final URL.

Fix

Two-line patch — re-prepend nextConfig.basePath to the Location header. Originally introduced in #80669, neither that PR nor #80157 (which gated the flag) accounted for basePath.

Pull request: vercel/next.js#93434 — includes a regression test under test/e2e/app-dir/segment-cache/cdn-cache-busting-base-path/.

cc the original authors of the relevant area: @acdlite (#80157, surrounding code) — happy to make any requested changes.

extent analysis

TL;DR

To fix the cache-poisoning defense issue with non-root basePath configurations, prepend the basePath to the Location header in the 307 redirect.

Guidance

  • Identify if your Next.js application uses a non-root basePath and is on a canary build where validateRSCRequestHeaders is enabled by default.
  • Verify if your application has any redirect chains that could land on an app-router page without an explicit _rsc= query parameter, which could trigger the cache-poisoning defense.
  • To mitigate the issue, consider applying a patch similar to the two-line fix mentioned, which involves prepending nextConfig.basePath to the Location header.
  • Test your application thoroughly after applying any fixes, especially focusing on redirect chains and cache behavior.

Example

// packages/next/src/server/base-server.ts
const url = new URL(req.url || '', 'http://localhost')
setCacheBustingSearchParamWithHash(url, expectedHash)
// Prepend basePath to the Location header
const location = `${this.nextConfig.basePath}${url.pathname}${url.search}`
res.statusCode = 307
res.setHeader('location', location)

Notes

This fix specifically addresses the issue with non-root basePath configurations in Next.js canary builds where validateRSCRequestHeaders is enabled. It may not apply to all configurations or versions of Next.js.

Recommendation

Apply the workaround by prepending basePath to the Location header in the 307 redirect, as this directly addresses the identified issue and prevents the browser from navigating outside the app's basePath.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING