fix(actions): return 404 + x-nextjs-action-not-found for missing actions - #1386
Conversation
Treat both build-time error shapes raised by `@vitejs/plugin-rsc` when a server-action reference is missing from the manifest — dev's `[vite-rsc] invalid server reference '<id>'` and prod's `server reference not found '<id>'` — as Next.js action-not-found. Previously only the dev-mode message was recognised, so production builds (and apps with no server actions defined at all) returned 500 instead of Next.js' 404 + `x-nextjs-action-not-found: 1` response. Both the fetch-action and progressive (no-JS) code paths benefit, since the latter calls `isServerActionNotFoundError(error, null)` before it has resolved an action id. Refs #1340
commit: |
|
|
/bigbonk review |
There was a problem hiding this comment.
Clean, focused fix. The before/after is clear:
Old behavior: isServerActionNotFoundError(error, null) early-returned on message.startsWith("Failed to find Server Action") — so only the vinext-generated prefix was matched. When actionId was non-null, only the dev-mode [vite-rsc] invalid server reference pattern was checked. The prod-mode server reference not found '<id>' shape was never recognised on either path, producing a 500 instead of the expected 404 + x-nextjs-action-not-found: 1.
New behavior: Both the actionId and !actionId branches now check both plugin error shapes (dev [vite-rsc] invalid server reference and prod server reference not found), so production builds and zero-server-action apps get the correct 404 response.
The code reads well — the actionId branch uses exact includes() with the id interpolated (no false positives from substring collisions), and the !actionId branch falls back to regex anchored on the distinctive prefix+quote. The comment block explaining the two error shapes and linking to the plugin source is thorough and will save future debuggers time.
Tests are well-structured: one for the fetch-action (RSC) path, one for the progressive (MPA) path, both asserting status, header, content-type, body text, and that reportRequestError is not called (since this is a controlled 404, not an internal error).
No concerns.
|
Approved PR #1386. The fix correctly recognises the prod-mode |
…#1340) (#1853) * fix(actions): return 404 for MPA action on app with no server actions (#1340) A multipart form POST to an App Router page is always a server-action attempt. When the form body decodes to no action at all (e.g. the build has no server actions, so decodeAction returns null rather than throwing), vinext fell through to a 200 page render instead of Next.js' 404 + x-nextjs-action-not-found. The fetch-action variant (via the Next-Action header) already worked; only the MPA/no-JS form-POST variant was wrong. Gate the new 404 on the posted-to route being a page: route handlers run after action dispatch and legitimately accept raw multipart POSTs, so they still fall through. The page-vs-route distinction uses the static __loadPage / __loadRouteHandler markers, available before lazy module hydration. Finishes the no-server-actions MPA case of #1340 (error-boundary, content-type, and unrecognized-action handling landed in #1386/#1668). * test(actions): add MPA no-action integration test; skip redundant route match (#1340) Address ask-bonk review on #1853: - Add a dev-server integration test that POSTs multipart/form-data to a page (/about) and asserts 404 + x-nextjs-action-not-found, locking in the entry-side matchRoute + __loadPage/__loadRouteHandler classification end-to-end (previously only unit-tested by passing hasPageRoute directly). - Gate the progressive-action route match on isProgressiveServerActionRequest so matchRoute no longer runs on every App Router request — only for actual multipart/no-actionId POST candidates.
Summary
Recognise the prod-mode error shape from
@vitejs/plugin-rscwhen a server-actionreference id is missing from the manifest, so apps return Next.js' standard
404 +
x-nextjs-action-not-found: 1response (withcontent-type: text/plainandbody
Server action not found.) instead of a generic 500.@vitejs/plugin-rscraises two different "no such server reference" errorsdepending on the build mode, and both mean the same thing:
[vite-rsc] invalid server reference '<id>'(reference validation virtual)server reference not found '<id>'(virtual:vite-rsc/server-referenceslookup, including the no-server-actions-at-all case)Only the dev-mode message was matched before, so production builds (and apps
with no server actions defined at all) fell through to the generic 500 path. The
progressive (no-JS) handler hits the same code path via
decodeAction(body)before it has resolved an action id, so the
actionId: nullbranch was widenedto recognise both error patterns too.
This addresses sub-problem #1 from #1340 (404 +
x-nextjs-action-not-found).Two sub-problems remain as follow-ups:
text/plain/ invalid-content-type action responses.decodeAction. Today vinext relies ondecodeActionraising one of the recognised reference errors; in the worst case it returns a non-function and we render the page (no 404).Refs #1340
Test plan
server reference not found '<id>'shape on the fetch-action path; confirmed it failed onmain(500 received, 404 expected) and now passes.pnpm test tests/app-server-action-execution.test.ts— 37 passed.pnpm run check— format + lint + types clean.