mirror of
https://github.com/vercel/next.js.git
synced 2026-09-20 02:25:18 +08:00
20892dd44e
Reopens #93710 from a branch on this repo so the required deploy test matrix can run — GitHub Actions doesn't expose secrets to fork PRs, and those deploy jobs are required checks. All commits and credit carry forward from the original PR by @elishagreenwald, which built on earlier attempts by @LiamBoz in #84525 and @claygeo in #92053. When middleware rewrites a Server Action POST to a page that doesn't bundle the action, the receiving worker forwards the request to a worker that does. The forwarded request hits middleware again and gets rewritten the same way, so the new receiving worker forwards again — looping until undici's headers timeout (~300s) or memory pressure brings the server down (#84504). The fix is two related changes on the forwarding code path. The first is an `!actionWasForwarded` guard in `action-handler.ts` so a request carrying `x-action-forwarded: 1` is never forwarded a second time. The second is a new branch in `createForwardedActionResponse` that copies the `x-nextjs-action-not-found` header and 404 status onto the originating response when the forwarded worker can't find the action either; without it the client would see a generic "unexpected response" error instead of the `UnrecognizedActionError` that the rest of the framework expects. The e2e test at `test/e2e/app-dir/action-forward-loop` reproduces the scenario. A `proxy.ts` rewrites POSTs to `/with-action` so GETs still render the page normally; the page renders a `<form>` with an inline server action wrapped in a React error boundary that branches on `unstable_isUnrecognizedActionError`. The test clicks the button and waits for `#action-not-found-error`. Without the loop guard the test times out at 10s, and without the pass-through the boundary catches a generic error and the assertion fails — so both changes are individually load-bearing. This is a short-term fix. The mid-term direction is to remove server-side action forwarding altogether and have the client dispatch each Server Action to the route that actually bundles it, which makes the entire forwarding code path (and this bug surface) unnecessary. #90549 is the draft exploring that approach. Fixes #84504 Closes #93710 Closes #84525 Closes #92053 --------- Co-Authored-By: Elisha Greenwald <ejgreenwald@gmail.com> Co-Authored-By: LiamBoz <thisamazingnow@gmail.com> Co-Authored-By: kmaclip <kmartclips@proton.me>
23 lines
726 B
TypeScript
23 lines
726 B
TypeScript
import { NextResponse } from 'next/server'
|
|
import type { NextRequest } from 'next/server'
|
|
|
|
// Only rewrite action POSTs so the user can still GET the page to see the
|
|
// button. The receiving worker is /without-action, which does not bundle the
|
|
// action — so without the loop guard, the worker forwards the action back to
|
|
// /with-action, which is rewritten here again, and so on.
|
|
export function proxy(request: NextRequest) {
|
|
if (
|
|
request.method === 'POST' &&
|
|
request.nextUrl.pathname === '/with-action'
|
|
) {
|
|
const url = request.nextUrl.clone()
|
|
url.pathname = '/without-action'
|
|
return NextResponse.rewrite(url)
|
|
}
|
|
return NextResponse.next()
|
|
}
|
|
|
|
export const config = {
|
|
matcher: '/with-action',
|
|
}
|