skip to content

In the Next.js App Router, what does the 'use server' directive actually do to a function, and why is it wrong to think of it as the mirror image of 'use client'?

level: juniorimportance: must knowfreq 70%

answer

  1. callability, not rendering
  2. the server is already the default
  3. only an id crosses to the browser
  4. opens a door back, not a bundle
  5. two legal positions, always async

basics

~20 s

'use server' marks a function as a server function that client code may call over the network — Next turns it into an RPC entry point. It does not make anything a Server Component; App Router components already run on the server by default.

solid answer

~50 s

`'use server'` is a *callability* marker, not a rendering marker. It tells the bundler that the functions it applies to must stay on the server and that Next should publish them as network-callable endpoints: at build time each one gets a generated id, and the client bundle receives only that id wrapped in a stub that POSTs back to the server. The function body, its imports and anything it closes over never reach the browser. That is why it is not the opposite of `'use client'`: in the App Router every component is already a Server Component unless a `'use client'` boundary says otherwise, so there is nothing for a "make this run on the server" directive to do. `'use client'` widens what ships to the browser; `'use server'` opens a door back the other way. A client module cannot declare one itself — it imports it from a `'use server'` module or receives it as a prop.

go deeper

for a junior

Be able to say in one sentence that the directive marks a function the client is allowed to call over the network, and that App Router components already run on the server without it. Do not describe it as the opposite of 'use client'.

for a middle

Explain the build mechanics: the body stays out of every client bundle, the function gets a generated id, and the client holds a stub that POSTs that id back. Name the two legal placements and the async-only rule.

for a senior

Show that you treat adding the directive as publishing an endpoint. Talk about how you keep that surface small — which modules carry it, what gets imported where — and why the calling UI proves nothing about who can invoke it.

for a principal

Own the boundary as an architectural decision: which mutations belong on an RPC surface Next generates for you versus a route handler you version explicitly, and how a team keeps the generated surface reviewable as the codebase grows.

## The one-sentence version `'use server'` marks a function as a **server function** (the thing Next calls a Server Action when it is wired to a form or an event): code that is guaranteed to execute on the server, and that client-side code is allowed to *call* even though it can never *see* it. ## What the build actually does When the compiler sees the directive, it does three things: 1. It keeps the function's body — and everything the body imports — out of every client bundle. 2. It assigns the function a **generated id** at build time. 3. It replaces the client-side reference with a small stub. Calling that stub does not run your code in the browser; it issues a POST back to the server carrying the action's id and the serialized arguments. The server looks the id up, runs the real function, and streams the result (plus any updated UI) back. So a client component that does `import { deletePost } from './actions'` ends up holding a *handle*, not an implementation: ```tsx // app/actions.ts 'use server' export async function deletePost(id: string) { await db.post.delete({ where: { id } }) // never shipped to the browser } ``` ```tsx // app/delete-button.tsx 'use client' import { deletePost } from './actions' export function DeleteButton({ id }: { id: string }) { // deletePost here is a stub that POSTs; the query above stays server-side return <button onClick={() => deletePost(id)}>Delete</button> } ``` ## Why it is not the counterpart of 'use client' The symmetry of the two strings is the trap. In the App Router the server is the **default**: every file under `app/` is server-side until a `'use client'` directive marks a boundary, and everything imported from a client module falls on the client side of that boundary. There is therefore no need for a directive that says "render this on the server" — that is the baseline. The two directives are better understood as answers to different questions: - `'use client'` answers *"what must be sent to the browser?"* It marks an entry point into the client module graph. - `'use server'` answers *"what may the browser call back into?"* It marks an entry point into the server, i.e. it defines an RPC surface. A useful mental check: `'use client'` makes the bundle bigger; `'use server'` makes the API surface bigger. ## Where the directive may appear It has exactly two legal positions: - as the first statement of a **module**, before any imports or code, in which case it applies to every export of that module; or - as the first statement inside an **async function body**, in which case it applies to that function only. In both positions the function it marks must be `async`, because the client always reaches it over the network and the call is therefore inherently asynchronous — the stub returns a promise no matter what the real body does. One placement that does **not** work: a module that starts with `'use client'` cannot declare a server function inline. A client module is compiled for the browser, so there is nothing there to keep on the server. The two ways a client component gets hold of a server function are importing it from a `'use server'` module, or receiving it as a prop from a Server Component that defined it. ## The consequence people miss Because the directive publishes an endpoint, adding it is a deployment decision, not just a code-organisation decision. Anyone who can reach your site can send a POST carrying an action's id and arguments of their choosing; the function is not "protected" by the fact that the only button that calls it is behind a login screen in your UI. Type annotations are erased at build time and do not validate anything at runtime either. Whatever guarantees a server function needs, it has to establish inside its own body. ## A small vocabulary note You will hear "Server Action" and "server function" used interchangeably. Newer Next and React docs use *Server Function* for the general concept and reserve *Server Action* for one passed to a form's `action` prop or otherwise used as a transition-driving mutation. Either term is fine in an interview as long as you can describe the mechanism above.

  • Can a file that begins with 'use client' declare its own server function with an inline 'use server'?
    No. A client module is compiled for the browser, so there is nothing there that could stay on the server, and Next rejects it. A client component gets a server function in one of two ways: it imports it from a module marked `'use server'`, or a Server Component defines it and passes it down as a prop.
  • I have an async helper that only a Server Component ever calls. Does adding 'use server' to it change anything?
    Yes, and not in your favour. Without the directive it is an ordinary server-side function, called in-process. With it, Next publishes it as a network-callable endpoint with its own id, so you have widened your public API for no benefit. Add the directive only when client code needs to hold a reference to the function.
  • What does the browser actually send when a client component calls an imported server function?
    A POST to the current route, carrying the action's build-time id and the serialized arguments. The function body is never in the bundle, so the browser cannot inspect or run it — it only knows the id. The server resolves the id, runs the real function, and returns the serialized result along with any re-rendered server output.

saying these in an interview costs you the question

  • Says 'use server' turns a file into a Server Component
  • Calls it the opposite of 'use client'
  • Thinks the action's code is shipped and run in the browser
  • Believes an unexported or UI-hidden action cannot be called directly
  • Adds it to every server-side helper as good practice

context