nextjs - ✅(Solved) Fix Forwarded server actions with middleware rewrites cause an infinite request loop [2 pull requests, 1 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#84504Fetched 2026-04-08 02:19:06
View on GitHub
Comments
0
Participants
1
Timeline
9
Reactions
19
Participants
Timeline (top)
cross-referenced ×4labeled ×3issue_type_added ×1referenced ×1

Root Cause

In this case, the forwarding makes a new request to /rewrite/ from /rewrite/other, because /rewrite/ is the page that defines the server action. However when the server action makes it to middleware, it gets rewritten to /rewrite/rewrite and the process continues forever.

Fix Action

Fixed

PR fix notes

PR #84525: Fix/action forward loop guard

Description (problem / solution / changelog)

What?

Fixes #84504

Add a minimal loop-detection guard to createForwardedActionResponse so a Server Action request that has already been forwarded (marked by x-action-forwarded=1) is not forwarded again. Instead, we return early (no second forward).

  • File: packages/next/src/server/app-render/action-handler.ts
  • Behavior change: if x-action-forwarded=1 is present on the incoming request, return from createForwardedActionResponse without forwarding again.

Why?

In apps that use Middleware rewrites, a forwarded Server Action request can be rewritten to a path that triggers forwarding again, causing an infinite loop (e.g. /rewrite/other → /rewrite/... → /rewrite/rewrite/... → …). This leads to repeated logs and a server action that never resolves.

This guard breaks the cycle with very low risk by only affecting requests that were already forwarded once.

How?

At the start of createForwardedActionResponse, inspect the incoming headers:

  • If x-action-forwarded=1 is present → return (do not forward again).
  • Otherwise, proceed as before and set x-action-forwarded=1 on the outgoing forwarded request.

Works in both Node and Edge:

  • If req.headers is already a Headers (Edge), use it.
  • Otherwise adapt Node headers via HeadersAdapter.

Test Plan

Manual (using the public repro)

  • With this patch built and packed into the repro app:
    • Trigger the fast Run action → Navigate sequence.
    • Confirm:
      • No repeated /rewrite/... requests or log spam.
      • The page no longer gets stuck due to an infinite forwarding loop.

Notes: This PR intentionally focuses on loop prevention. A broader follow-up could make forwarded requests mirror the pre-rewrite target to ensure resolution semantics remain identical under rewrites. That would be a separate change.

Risk

Low:

  • Only affects requests that already bear x-action-forwarded=1.
  • Does not change the initial legitimate forward.
  • Keeps all other server action behavior intact.

For Contributors Checklist

  • Bug fix
  • Related issue linked (Fixes #84504)
  • Repro steps included (public repo)
  • Ran pnpm prettier-fix locally
  • Tests: this is a minimal guard; happy to add a small e2e that simulates a rewrite + quick navigation if maintainers prefer (guidance welcome)

Changed files

  • packages/next/src/server/app-render/action-handler.ts (modified, +14/-0)

PR #92053: fix: prevent infinite request loop when forwarding server actions with middleware rewrites

Description (problem / solution / changelog)

Problem

Fixes #84504

When a server action fires right before navigation, Next.js forwards the action to the correct worker via createForwardedActionResponse. If middleware applies a rewrite to the forwarded request, the rewritten URL arrives at a worker that also wants to forward, creating an infinite request loop that never resolves.

Root Cause

In action-handler.ts, the forwarding guard at line 713 checks if (actionId) but does not check whether the action was already forwarded:

const actionWasForwarded = Boolean(req.headers['x-action-forwarded'])  // line 711: READ but not used as guard

if (actionId) {  // line 713: missing !actionWasForwarded check
  const forwardedWorker = selectWorkerForForwarding(actionId, page)
  if (forwardedWorker) {
    return createForwardedActionResponse(...)  // forwards again → infinite loop
  }
}

The x-action-forwarded header is already set on outgoing forwarded requests (createForwardedActionResponse, line 221) and read on incoming requests (line 711). The loop detection mechanism exists, it's just not wired into the forwarding guard.

Fix

Add !actionWasForwarded to the forwarding condition:

if (actionId && !actionWasForwarded) {

If an action has already been forwarded once (header present), skip the forwarding logic entirely. The action will be handled by the current worker instead of bouncing infinitely between workers.

5 lines changed in 1 file.

Note on PR #84525

PR #84525 addresses the same issue but has been abandoned for 6 months with zero reviews. This PR takes the same conceptual approach (check x-action-forwarded header) with a cleaner implementation at the caller level rather than inside createForwardedActionResponse.

Changed files

  • packages/next/src/server/app-render/action-handler.ts (modified, +35/-11)

Code Example

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 24.6.0: Mon Aug 11 21:15:09 PDT 2025; root:xnu-11417.140.69.701.11~1/RELEASE_ARM64_T6041
  Available memory (MB): 49152
  Available CPU cores: 14
Binaries:
  Node: 22.15.1
  npm: 10.9.2
  Yarn: 1.22.22
  pnpm: N/A
Relevant Packages:
  next: 15.5.4 // Latest available version is detected (15.5.4).
  eslint-config-next: 15.5.4
  react: 19.1.0
  react-dom: 19.1.0
  typescript: 5.9.3
Next.js Config:
  output: N/A
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/blairmcalpine/forwarded-server-action-request-loop-repro

To Reproduce

  1. npm i
  2. npm run dev
  3. Visit localhost:3000
  4. Click "run action" and then quickly (<500ms in between) click "Navigate to other page".
  5. Notice that (1) the server action fired by the "run action" button never resolves and (2) the server logs print out a ton of messages.

Current vs. Expected behavior

Current: When a server action gets forwarded (which happens when it fires after navigation to another page that doesn't know about that server action) and gets rewritten by middleware, it causes an infinite request loop.

Expected: Server action properly resolves and no request loop happens.

For full context, here is where this server action forwarding functionality was added.

In this case, the forwarding makes a new request to /rewrite/ from /rewrite/other, because /rewrite/ is the page that defines the server action. However when the server action makes it to middleware, it gets rewritten to /rewrite/rewrite and the process continues forever.

My suggestion for a solution would be (1): At a minimum, add a loop detection check using the x-action-forwarded header. If an incoming action has been forwarded already, don't forward it again no matter what.

A real solution though would likely look like making the forwarded server action request in the same format that the incoming one had. In this case, because the incoming request was made to /other and rewritten to /rewrite/other, the forwarded server request should not include the rewrite.

One can argue that you just shouldn't do a rewrite on these forwarded actions (which is what we've done at Faire to work around this issue), however this forwarded action behaviour is completely internal and undocumented, so it's not clear that anywhere that this would even cause that problem. Regardless of a solution here, I believe this behaviour should be documented as well. It can cause a lot of weirdness and problems in self-hosted setups and custom server wrappers especially where __NEXT_PRIVATE_ORIGIN is undefined, in which the server ends up making a request all the way back to the public domain.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 24.6.0: Mon Aug 11 21:15:09 PDT 2025; root:xnu-11417.140.69.701.11~1/RELEASE_ARM64_T6041
  Available memory (MB): 49152
  Available CPU cores: 14
Binaries:
  Node: 22.15.1
  npm: 10.9.2
  Yarn: 1.22.22
  pnpm: N/A
Relevant Packages:
  next: 15.5.4 // Latest available version is detected (15.5.4).
  eslint-config-next: 15.5.4
  react: 19.1.0
  react-dom: 19.1.0
  typescript: 5.9.3
Next.js Config:
  output: N/A

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

Server Actions, Middleware

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

next dev (local), next start (local), Other (Deployed)

Additional context

No response

<sub>NEXT-4736</sub>

extent analysis

TL;DR

Implement a loop detection check using the x-action-forwarded header to prevent infinite request loops when server actions are forwarded and rewritten by middleware.

Guidance

  • Add a check for the x-action-forwarded header in the middleware to detect if an incoming action has been forwarded already.
  • If the header is present, do not forward the action again to prevent the infinite loop.
  • Consider modifying the forwarded server action request to match the original incoming request format, excluding any rewrites.
  • Review the Next.js documentation and consider contributing to the documentation of this internal and undocumented behavior to help prevent similar issues in the future.

Example

No code example is provided as the issue does not include specific code snippets that can be modified to fix the issue.

Notes

The provided environment information and package versions may be relevant when troubleshooting the issue, but do not directly impact the suggested fix.

Recommendation

Apply a workaround by implementing the x-action-forwarded header check to prevent infinite loops, as the root cause of the issue is related to the internal and undocumented behavior of server action forwarding.

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