next.js
ad27ddab - fix: respond with 404 for unrecognized action ids (#77012)

Commit
195 days ago
fix: respond with 404 for unrecognized action ids (#77012) > [!IMPORTANT] > view this diff with "hide whitespace changes", it's unreadable otherwise. This is an alternative approach to #80613. We touch a lot of same areas but do different changes there, and i also have a bunch more changes stacked on top, so the plan is to just revert that and do this instead. --- server action calls with an unrecognized action id (i.e. one not found in `serverModuleMap`) have been returning a 200 instead of a 404 for quite some time. and also rendering the page they were called on. ### The problem this happened due to an unfortunate mistake. `handleAction` has a section like this: ```ts await actionAsyncStorage.run( { isAction: true }, // "the callback" async () => { ... } ); return { type: 'done', result: actionResult, formState, } ``` inside that callback, we have many places that validate the action id and try to bail out if it's not recognized: ```ts try { actionModId = getActionModIdOrError(actionId); } catch (err) { console.error(err); return { type: 'not-found '} } ``` this was clearly intended to be an early return from `handleAction`. but it didn't work, because outside the callback we're doing ```ts await actionAsyncStorage.run(...) ``` i.e. **discarding the callback's return value**. So instead of returning `'not-found'` as intended, we end up falling through to the `'done'` return right after, and returning `{ type: 'done', result: undefined, formState: undefined }`. then, our caller,`renderToHTMLToFlightImpl` looks at that what we returned, and sees a 'done' https://github.com/vercel/next.js/blob/768183c5ddbaa94c1f6004816a1fae282fa0ab04/packages/next/src/server/app-render/app-render.tsx#L1522-L1547 but because if there's no `result` or `formState`, it'll fall through the conditionals and do the same thing it'd do if we returned `null/undefined` (i.e. if `!actionRequestResult`). so we end up on the rendering path, render a page, and finally respond with a 200. ### The fix, part 1 This PR fixes this. we're now directly returning the value from the callback (`return await actionAsyncStorage.run(...)`) . i got rid of all the weird fallthroughs and made each of the branches return something explicitly. I've also changed `handleAction`'s return type to have a `... | null` instead of `... | undefined` (which is interpreted as "this was not an action request") so that it's not possible to fall off the end of the function without an explicit return. ### The fix, part 2 To make this more complicated, returning `'not-found'` for fetch actions was actually also incorrect! that value makes `renderToHTMLToFlightImpl` render an HTML 404 page, which is pretty useless as a (fetch) action response -- it's got the wrong content type! (#80613 relied on this to make the action fail on the client-side, which isn't ideal, because the error message doesn't indicate what the problem is). To solve this, `handleAction` now returns a custom 404 response tagged with a `x-nextjs-action-not-found` header (and uses `{ type: 'done' }` instead of `{ type: 'not-found' }` to bypass the aformentioned 404 handling). Then, on the client, `fetchServerAction` checks for this header, and throw a corresponding error. Other 404s will still throw a `An unexpected response was received from the server.` error instead, because they had to come from something else, like a bad proxy rewrite.
Author
Parents
Loading