skip to content

A Next.js App Router page renders <form action={saveNote}> where saveNote is a Server Action, and a browser submits it without ever running the page's JavaScript. Trace that request: where does the POST go, how does Next decide which action to run, and what comes back?

level: middleimportance: should knowfreq 45%

answer

  1. no dedicated API route is involved
  2. posts back to the page's own URL
  3. hidden fields name the action
  4. the response is a whole document
  5. redirect() becomes a real HTTP redirect

basics

~20 s

The browser POSTs to the page's own URL. The submitted body carries hidden fields identifying the Server Action, which Next matches to the action module and runs on the server. Next then re-renders the route and returns a full HTML document, or a redirect if the action called redirect().

solid answer

~50 s

The server-rendered form is an ordinary `<form method="post">` pointing at the URL of the page that rendered it — there is no separate `/api` endpoint. Alongside the user's named inputs, React emits hidden fields identifying which Server Action this form targets. Next sees a POST to a route, reads that identifier out of the body, resolves it to the exported action, hands it a `FormData` built from the submitted fields, and awaits it. Because no client runtime is present, the response cannot be an incremental payload: Next re-renders the route on the server and returns a complete HTML document, which the browser treats as a normal navigation. If the action called `redirect()`, the browser follows an HTTP redirect instead; cookies written with `cookies()` ride along on that same response. This assumes the App Router in Next 15/16 with React 19 Server Actions.

go deeper

for a junior

Know that the form posts back to the same page URL and that the server runs the action and sends back a whole page, rather than the page calling an API you wrote.

for a middle

Walk the full path out loud: hidden identifier fields in the body, Next resolving them to the exported action, FormData reconstructed server-side, and a full HTML document or redirect in response.

for a senior

Show what this implies operationally — post-mutation redirect() so refreshes do not re-POST, cookies riding the same response, and the fact that returned state cannot reach a non-hydrated user.

for a principal

Reason about the contract this creates: the route URL is the mutation surface, action ids are generated and not a stable public API, and any client outside your app should be given an explicit endpoint instead.

## The shape of the request The first thing to be clear about: there is no dedicated action endpoint. The form posts back to **the URL of the page that rendered it**. Next's routing already owns that path, and a POST to a route with a Server Action payload is handled by the framework rather than by any `route.ts` you wrote. The body is a normal form submission — `multipart/form-data` when there is a file input, URL-encoded otherwise. It contains two kinds of things: 1. The user's fields, exactly as `name` attributes describe them. 2. Hidden fields React emitted, which identify *which* Server Action this form targets, plus any arguments bound into the action. That identifier is the crux. Every exported Server Action gets a stable id at build time so that a client — or, here, a plain browser form — can name it without shipping the function body. Without JavaScript, that id travels as form data instead of as an RPC argument, which is precisely why the no-JS path works at all. ## Server side Next matches the incoming id to the action, reconstructs a `FormData` object from the submitted fields, and invokes the action with it. The action runs exactly as it would have for a hydrated client submission — same code, same environment, same access to `cookies()` and `headers()`. Nothing about the action's implementation is aware of which path invoked it, which is the property that makes progressive enhancement cheap to keep: you do not maintain two mutation paths. This is also why the security posture is unchanged in either path — the action is a public POST endpoint that must authenticate and validate its own input regardless of how it was reached. ## The response Here is where the two paths diverge. A hydrated client submission gets back a payload the React runtime applies in place, patching the current tree without a navigation. A no-JS submission cannot: there is no runtime to apply anything. So Next renders the route on the server after the action completes and returns a **complete HTML document** for that URL. The browser treats it as an ordinary form navigation — new document, fresh paint, scroll reset, back-button entry. Two variations matter: - **`redirect()` inside the action.** The response becomes an HTTP redirect and the browser follows it, landing on the target page. This is the standard way to make the post-mutation destination correct in both paths; it is also what keeps a no-JS user from sitting on a URL that would re-POST on refresh. - **Cookies.** `cookies().set(...)` inside the action attaches `Set-Cookie` to that same response, so a login or session-writing action works identically without JS. A value *returned* from the action, by contrast, has nowhere to go in the no-JS path — there is no client hook to receive it. Anything the user must see has to be reflected in what the re-rendered page shows, which usually means the action writes state (or a cookie) that the next render reads. ## Why the mechanics matter in an interview Candidates often assume Next generates an `/api/actions` route, or that the action id is the function's name. Both are wrong in ways that lead to bad designs — the first invites people to "call the endpoint directly" from other clients, the second suggests renaming a function is a safe refactor for callers. The truthful model is: **the route URL is the endpoint, and an opaque generated id selects the action.** The second common gap is expecting the same response in both paths. Knowing that the no-JS path ends in a full document navigation is what tells you why post-mutation `redirect()` is not optional decoration and why relying purely on returned state to communicate success leaves the non-hydrated path silent. ## Quick way to see it Disable JavaScript in DevTools, submit the form, and watch the Network panel: one POST to the page's own path, and a document-type response (or a 3xx followed by a document). That single observation contains the whole mechanism.

  • If the action returns a value, where does it go in the no-JavaScript path?
    Nowhere the user can see. Return values are delivered to client-side state, and no client runtime is running. To communicate a result on that path the action must change something the next server render reads — persisted state, a cookie, or a `redirect()` to a URL that renders the message.
  • Does a POST to that URL require a route handler to exist for the route?
    No. Next handles Server Action POSTs itself based on the action identifier in the body; you do not write a `route.ts` for it, and adding one is not how you receive action submissions. Route handlers are a separate surface for requests you address by URL and method yourself.
  • Why does the response differ between the hydrated and non-hydrated submission of the same form?
    Because the client runtime is what applies an incremental update in place. With it, Next can send a payload React patches into the existing tree, preserving scroll and client state. Without it, the only thing a browser can consume is a full HTML document, so the submission ends as a normal navigation.

saying these in an interview costs you the question

  • Thinks Next exposes actions under a generated /api route
  • Says the action is selected by its function name
  • Expects an RSC payload to arrive with no client runtime
  • Assumes a route handler must exist to receive the POST
  • Believes returned action state is visible without JavaScript

context