Skip to content

fix: prevent duplicate Location and x-nextjs-stale-time headers on redirect#92057

Open
claygeo wants to merge 3 commits intovercel:canaryfrom
claygeo:fix/duplicate-location-header-redirect
Open

fix: prevent duplicate Location and x-nextjs-stale-time headers on redirect#92057
claygeo wants to merge 3 commits intovercel:canaryfrom
claygeo:fix/duplicate-location-header-redirect

Conversation

@claygeo
Copy link
Copy Markdown

@claygeo claygeo commented Mar 28, 2026

Problem

Fixes #82117

Redirect responses include duplicate Location and x-nextjs-stale-time headers. Behind Cloudflare or similar proxies that merge duplicate headers, Location: /redirect becomes Location: /redirect, /redirect, which is an invalid redirect target that breaks navigation entirely.

Regression introduced in 15.4.1. Multiple users confirmed; reverting to 15.4.0 resolves it.

Root Cause

Two code paths set the same headers on the same response object:

Source 1 (render phase): app-render.tsx:3482 calls setHeader('location', redirectUrl) which sets the header on the NodeNextResponse and stores it in metadata.headers for caching.

Source 2 (cache serving): app-page.ts:1611-1624 iterates cachedData.headers and calls res.appendHeader(key, value) using the native ServerResponse.appendHeader, which unconditionally appends without checking for existing values.

render phase:     res.setHeader('Location', '/redirect')     ← sets once
cache serving:    res.appendHeader('Location', '/redirect')  ← appends again
result:           Location: /redirect, /redirect             ← duplicate!

Fix

In the cache header application loop (app-page.ts:1611-1624), use setHeader (replace) instead of appendHeader (add) for headers that don't support multiple values. Keep appendHeader for headers that are multi-value by HTTP spec: set-cookie, vary, www-authenticate, proxy-authenticate.

This matches the same multi-value allowlist used in send-response.ts:35-41.

claygeo added 3 commits March 28, 2026 13:13
When a malformed request body is sent to a server action endpoint
(e.g. by vulnerability scanners probing for CVE-2025-55182),
decodeReply throws a SyntaxError that bubbles up to the generic
catch handler and returns HTTP 500. This is incorrect — a malformed
client request should return 400 Bad Request, not 500 Internal
Server Error.

Wrap the decodeReply calls in both the edge/web runtime and node
runtime text body paths with try/catch for SyntaxError. Only
SyntaxError is caught and mapped to 400; all other errors are
re-thrown to preserve existing error handling behavior.

Fixes vercel#86945
…h middleware rewrites

When a server action fires during navigation, Next.js forwards the
action to the correct worker. If middleware applies a rewrite to the
forwarded request, the rewritten request arrives at a worker that also
tries to forward, creating an infinite loop.

The x-action-forwarded header is already set on forwarded requests
(createForwardedActionResponse, line 221) and read on incoming requests
(line 711), but the forwarding guard at line 713 only checks actionId
without checking whether the action was already forwarded. Add the
!actionWasForwarded condition to prevent re-forwarding.

Fixes vercel#84504
…direct

When a page redirect is rendered, the render phase sets Location via
setHeader on the response. Then the cache serving code in app-page.ts
re-applies cached headers using the native appendHeader, which appends
unconditionally without checking for existing values. This produces
duplicate Location headers (e.g. Location: /redirect, Location: /redirect).

Behind Cloudflare or similar proxies, duplicated Location headers get
merged into "Location: /redirect, /redirect" which is an invalid
redirect target, breaking navigation entirely.

Use setHeader (replace) instead of appendHeader (add) for headers that
don't support multiple values. Keep appendHeader for set-cookie, vary,
www-authenticate, and proxy-authenticate which are multi-value by spec.

Fixes vercel#82117
@nextjs-bot
Copy link
Copy Markdown
Collaborator

Allow CI Workflow Run

  • approve CI run for commit: 7adeb8e

Note: this should only be enabled once the PR is ready to go and can only be enabled by a maintainer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Duplicate Location and x-nextjs-stale-time in Header using redirect/permanentRedirect alongside dynamic 'force-static'

2 participants