nextjs - 💡(How to fix) Fix Next.js Server Action redirects send 3xx responses with bodies framed only by `Transfer-Encoding`, which some proxies/CDNs strip [2 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#92882Fetched 2026-04-17 08:25:44
View on GitHub
Comments
2
Participants
2
Timeline
6
Reactions
0
Timeline (top)
commented ×2subscribed ×2issue_type_added ×1labeled ×1

Root Cause

  1. Use 200 instead of 303 for Server Action responses. The current 303 status doesn't appear to serve any semantic purpose — browsers never follow it as a redirect because the response is consumed by the Next.js client-side fetch(). Switching to 200 (with the redirect URL communicated via x-action-redirect as is already happening) would sidestep the entire class of intermediary issues. But I suppose it could cause other issues, 200s might be cached by a CDN/reverse proxy?

Code Example

"use server";
  export async function submitPostcode(formData: FormData) {
    // ... process ...
    redirect("/");
  }

---

HTTP/1.1 303 See Other
Transfer-Encoding: chunked
Content-Type: text/x-component
X-Action-Redirect: /;push
X-Action-Revalidated: 1
 
1:"$Sreact.fragment"
2:I[68172,[],""]
...

---

HTTP/1.1 303 See Other
Content-Length: 0
X-Action-Redirect: /;push
X-Action-Revalidated: 1
 
(empty)

---

- Next.js: 15.x
- React: 19.x
- Deployment: Vercel
- Intermediary: [Imperva](https://www.imperva.com) (Incapsula) CDN/WAF
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://codesandbox.io/p/devbox/bold-dhawan-76ycf2

To Reproduce

I've made a reproduction but the issue is only visible when the Next.js site is accessed from behind a proxy like Imperva.

Instead of steps to reproduce, I'll attach two HAR files that show how the body gets stripped behind Imperva.

missing-response-body-issue-on-imperva.har.txt no-missing-response-body-issue-when-bypassing-imperva.har.txt

I've also attached a video demo that we originally provided to Imperva when we raised this as an issue with them:

https://github.com/user-attachments/assets/312c43e4-920d-4222-97a5-0a5828a996b9

Current vs. Expected behavior

When a Server Action calls redirect(), Next.js responds with 303 See Other and the RSC flight payload in the response body, framed using Transfer-Encoding: chunked with no Content-Length header. The client-side router reads both the x-action-redirect header and the body to perform a soft navigation.

Imperva (acting as our CDN/WAF in front of Vercel) strips the response body on 3xx responses when both Content-Length and Transfer-Encoding are absent on the origin response (see "Imperva's response" below). The headers reach the browser but the body is silently dropped, causing the Next.js router to fall back to a full hard navigation and/or for the page content to not be updated properly as there is no RSC payload to swap out content. We see this on our site when a user tries to change their postcode via a server action that ends with a redirect, but then after the action runs the site's header (in a layout file) shows stale data.

We would like Next.js to consider not using a 3xx status code for responses that carry a semantically meaningful body.

Repro

  • Next.js 15.x, App Router, Server Actions

  • A Server Action that calls redirect():

    "use server";
    export async function submitPostcode(formData: FormData) {
      // ... process ...
      redirect("/");
    }
  • A form that invokes the action via standard <form action={submitPostcode}>.

  • Deploy behind an HTTP intermediary that treats bodyless-framed 3xx responses as having no body (RFC-compliant behaviour — see below).

Observed behaviour

The POST to the Server Action returns:

HTTP/1.1 303 See Other
Transfer-Encoding: chunked
Content-Type: text/x-component
X-Action-Redirect: /;push
X-Action-Revalidated: 1
 
1:"$Sreact.fragment"
2:I[68172,[],""]
...

Directly from origin:

The client sees the full ~430 KB RSC payload and performs a soft navigation.

Through Imperva:

The client sees:

HTTP/1.1 303 See Other
Content-Length: 0
X-Action-Redirect: /;push
X-Action-Revalidated: 1
 
(empty)

The headers are preserved but the body is removed. The Next.js router receives an empty body, cannot complete the transition, and falls back to a full browser reload or fails to update the page's content.

Imperva's response

Imperva confirmed this is deliberate behaviour on their end, triggered by a specific configuration:

The Imperva proxy intentionally strips response bodies on 3xx responses under specific conditions:

The proxy will strip the body from 3xx responses when both of the following are true:

  • The origin response is missing both Content-Length and Transfer-Encoding headers
  • The site has the following configuration enabled: nocontentlengthbehavior = NCLB_COMPLIANT_PLUS_REDIRECTS

When these conditions are met, the proxy assumes that the 3xx response has no body and stops reading after the origin response headers, forwarding only the status code and headers to the client.

Resolution options:

  1. Modify the Misc Flag configuration: Set nocontentlengthbehavior to NCLB_OLD
  2. Update the origin server to comply with RFC requirements by including either a valid Content-Length header, or Transfer-Encoding: chunked, along with a proper Location header in the 3xx response.

Clearly a Location header does not make sense as this is an App Router navigation, not a classic browser navigation, but perhaps given it's not a classic browser navigation then a 200 response would be more appropriate than 303? Or is there a risk of CDN caching issues if a 2xx response is used?

We're working with them on trying the nocontentlengthbehavior = NCLB_OLD setting, but it doesn't seem to have helped – I will follow this up with them but I wanted to raise this issue in parallel to see if there's any possibility of the Next behaviour changing.

What (I think?) the specs say

I think Next.js is doing an okay thing here and I think perhaps Imperva is in the wrong. The below is based on my limited understanding and written with the help of Claude/Opus:

RFC 7230 §3.3.3 (and its successor RFC 9110 §6.4.1) is explicit about which responses may not carry a body:

All 1xx (Informational), 204 (No Content), and 304 (Not Modified) responses do not include a message body. All other responses do include a message body, although the body might be of zero length.

3xx responses (including 303) are in the "all other responses" category. The specs make no special framing rule for 3xx responses. A 3xx response can legitimately have a body, and intermediaries should not assume otherwise.

RFC 9110 §15.4.4 (303 See Other) does not specify whether the response has a body.

RFC 9112 §6 defines how a recipient determines message-body length. For a response without Content-Length or Transfer-Encoding, the rule is to read until the connection is closed — not to assume the body is empty.

So strictly speaking, Imperva's behaviour is non-compliant: they are inferring body-absence from the response status code, which the spec does not license.

In an ideal world, perhaps Next.js should not rely on 3xx-with-body being honoured end-to-end as Next.js does not know what proxies people will put in front of it.

Why this matters for Next.js specifically

The 303 with a body pattern is unusual. Historically, 3xx responses on the web have been bodyless – the Location header was what did all the work. Next.js reuses the 303 status code but without a Location header and with a body, and that seems to be confusing Imperva and perhaps other proxies?

Possible ideas for solutions

I don't know if either of these would be practical:

  1. Use 200 instead of 303 for Server Action responses. The current 303 status doesn't appear to serve any semantic purpose — browsers never follow it as a redirect because the response is consumed by the Next.js client-side fetch(). Switching to 200 (with the redirect URL communicated via x-action-redirect as is already happening) would sidestep the entire class of intermediary issues. But I suppose it could cause other issues, 200s might be cached by a CDN/reverse proxy?

  2. Send Content-Length when the body is fully computed. When the action is a simple redirect() and the RSC payload can be buffered, emit Content-Length explicitly. This removes the ambiguity that Imperva (and potentially others) are responding to. I don't know if this is possible though as the Server Action might be streaming? I appreciate this probably isn't an option.

Provide environment information

I know the Vercel advice is not to use a proxy in front of Vercel (fair enough, but we have to use it) but this would also affect self-hosted Next.js users who had their Next.js container behind Imperva.

- Next.js: 15.x
- React: 19.x
- Deployment: Vercel
- Intermediary: [Imperva](https://www.imperva.com) (Incapsula) CDN/WAF

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

Server Actions

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

Other (Deployed)

Additional context

No response

extent analysis

TL;DR

Consider using a 200 status code instead of 303 for Server Action responses to avoid intermediary issues with proxies like Imperva.

Guidance

  • Review the use of 303 status code for Server Action responses and consider switching to 200 to sidestep intermediary issues.
  • Investigate the possibility of sending Content-Length when the body is fully computed to remove ambiguity for intermediaries.
  • Verify the behavior of Imperva and other proxies with the current implementation to understand the scope of the issue.
  • Test the proposed solutions to ensure they do not introduce other issues, such as caching problems with CDNs or reverse proxies.

Example

// Instead of using redirect() with 303 status code
// export async function submitPostcode(formData: FormData) {
//   // ... process ...
//   redirect("/");
// }

// Use a 200 status code and communicate the redirect URL via x-action-redirect header
export async function submitPostcode(formData: FormData) {
  // ... process ...
  return NextResponse.json({ url: "/" }, { status: 200, headers: { "x-action-redirect": "/" } });
}

Notes

The proposed solution may have implications for caching and should be thoroughly tested to ensure it does not introduce other issues. Additionally, the behavior of other proxies and intermediaries should be verified to ensure the solution is robust.

Recommendation

Apply a workaround by using a 200 status code instead of 303 for Server Action responses, as this is a more straightforward solution that avoids intermediary issues. However, this should be thoroughly tested to ensure it does not introduce other problems.

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