---
title: "15 — Server Functions — Mutations That Run on the Server"
uid: server-functions
tags: ["roadmap:nextjs", "mutations", "server-functions", "server-actions", "forms", "nextjs"]
excerpt: "Server Functions are async functions that execute on the server, callable from Client or Server Components — the App Router's native answer to mutations, replacing hand-rolled endpoints."
date: 2026-08-13T03:28:00+0000
source: https://www.aveshina.my.id/en/blog/server-functions
---

The App Router's mechanism for mutations replaces a pattern I had been hand-rolling for years — endpoint, fetch, response handling. The model that clicked: **a Server Function is an async function that executes on the server, callable from Client or Server Components, used to handle form submissions and data mutations** [1]. In a mutation context they're called Server Actions. They replace the older pattern of writing a separate API endpoint, wiring up a fetch to call it, and handling the response — the function call crosses the network boundary for me.

## The mechanism

A Server Function is marked with the 'use server' directive, either at the top of a file (marking every export as a server function) or inside a function body. Once marked, the function runs only on the server — even when invoked from a Client Component, the call is serialized, sent to the server, executed, and the result returned [1].

```
// app/actions.ts
'use server';

import { revalidateTag } from 'next/cache';
import { db } from '@/lib/db';

export async function createPost(formData: FormData) {
  await db.post.create({
    data: { title: String(formData.get('title')) },
  });
  revalidateTag('posts'); // refresh the cached list
}
```

The win is that the Client Component doesn't write a fetch — it just calls the function:

```
// app/posts/NewPostForm.tsx
'use client';
import { createPost } from '@/app/actions';

export function NewPostForm() {
  return (
    <form action={createPost}>
      <input name="title" />
      <button type="submit">Create</button>
    </form>
  );
}
```

The action prop on a <form> accepts a Server Function directly — progressive enhancement included: the form works even before JavaScript hydrates, because it degrades to a standard POST.

```figure
<svg viewBox="0 0 720 280" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Hand-rolled mutation flow versus Server Function flow. Top: client writes a fetch to an API route handler, which writes to the DB and revalidates — the network call is explicit and the handler is a separate file. Bottom: the client calls the Server Function directly; the framework carries the call across the network, the function runs on the server, writes to the DB, and revalidates — one function, no manual fetch or handler.">
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">

    <!-- Hand-rolled -->
    <text x="360" y="22" font-size="12" font-weight="700" fill="#7f1d1d" text-anchor="middle">Hand-rolled — client fetches a separate API route</text>
    <rect x="30" y="38" width="120" height="40" rx="8" fill="#fee2e2" stroke="#dc2626" stroke-width="1.5"/>
    <text x="90" y="62" font-size="10" font-weight="700" fill="#7f1d1d" text-anchor="middle">client</text>
    <text x="155" y="58" font-size="9" fill="#7f1d1d" text-anchor="middle">fetch()</text>
    <line x1="150" y1="58" x2="200" y2="58" stroke="#dc2626" stroke-width="1.5"/>
    <rect x="200" y="38" width="140" height="40" rx="8" fill="#fee2e2" stroke="#dc2626" stroke-width="1.5"/>
    <text x="270" y="58" font-size="10" font-weight="700" fill="#7f1d1d" text-anchor="middle">API route handler</text>
    <text x="270" y="72" font-size="9" fill="#7f1d1d" text-anchor="middle">(separate file)</text>
    <line x1="340" y1="58" x2="380" y2="58" stroke="#dc2626" stroke-width="1.5"/>
    <rect x="380" y="38" width="120" height="40" rx="8" fill="#fee2e2" stroke="#dc2626" stroke-width="1.5"/>
    <text x="440" y="54" font-size="10" font-weight="700" fill="#7f1d1d" text-anchor="middle">database</text>
    <text x="440" y="68" font-size="9" fill="#7f1d1d" text-anchor="middle">+ revalidate</text>
    <text x="620" y="62" font-size="10" font-style="italic" fill="#7f1d1d" text-anchor="middle">explicit network call</text>

    <!-- Server Function -->
    <text x="360" y="150" font-size="12" font-weight="700" fill="#052e16" text-anchor="middle">Server Function — one function, implicit network call</text>
    <rect x="30" y="166" width="120" height="40" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="90" y="190" font-size="10" font-weight="700" fill="#052e16" text-anchor="middle">client</text>
    <text x="155" y="186" font-size="9" fill="#052e16" text-anchor="middle">createPost()</text>
    <line x1="150" y1="186" x2="200" y2="186" stroke="#16a34a" stroke-width="1.5" stroke-dasharray="4 3"/>
    <text x="175" y="178" font-size="8" font-style="italic" fill="#052e16" text-anchor="middle">framework</text>
    <text x="175" y="208" font-size="8" font-style="italic" fill="#052e16" text-anchor="middle">carries the call</text>
    <rect x="200" y="166" width="160" height="40" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="280" y="186" font-size="10" font-weight="700" fill="#052e16" text-anchor="middle">Server Function</text>
    <text x="280" y="200" font-size="9" fill="#052e16" text-anchor="middle">(runs on server)</text>
    <line x1="360" y1="186" x2="400" y2="186" stroke="#16a34a" stroke-width="1.5"/>
    <rect x="400" y="166" width="120" height="40" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="460" y="182" font-size="10" font-weight="700" fill="#052e16" text-anchor="middle">database</text>
    <text x="460" y="196" font-size="9" fill="#052e16" text-anchor="middle">+ revalidate</text>
    <text x="620" y="190" font-size="10" font-style="italic" fill="#052e16" text-anchor="middle">no manual fetch/handler</text>

    <text x="360" y="252" font-size="10" font-style="italic" fill="#64748b" text-anchor="middle">the reference crosses the network; the body and its server-only imports stay on the server</text>
  </g>
</svg>
```

## Why this replaces hand-rolled APIs

Before Server Functions, a mutation meant: write an API route handler, write a Client Component that fetches it on submit, handle loading and error states, then revalidate the cache. Server Functions collapse that into one async function — the network call is implicit, and because the function runs on the server, it can directly call the database, the CMS, or any server-only API without exposing those credentials to the browser [1].

The security model matters and is worth stating plainly: Server Functions are server-side code. Anything they import and anything they do stays on the server. The function reference sent to the client is just that — a reference — not the function body. This is why they're the right place for mutations that touch sensitive systems.

## Pairing with revalidation

A Server Function typically ends with a revalidation call (revalidateTag or revalidatePath) so the cache reflects the mutation. The pattern: write the data, bust the affected cache tag, the next read serves the fresh result. This composes Server Functions with the caching layer — mutations invalidate the cache, and the rendering layer picks up the new data on the next request.

## How I use this

Server Functions are my default for every mutation — form submissions, likes, comments, guestbook entries, anything that writes data. The function lives in app/actions/ (or co-located with the component), runs on the server, touches the database directly, and revalidates the relevant cache tag at the end. The Client Component just calls it and (usually) wires up optimistic updates. I reach for a hand-rolled API route only when I need a non-Next.js client to call the endpoint — webhooks, third-party integrations, public APIs. For internal mutations, Server Functions are simpler and safer.

## References

[1] Vercel, "What are Server Functions?," Next.js Docs, 2024. [Online]. Available: [https://nextjs.org/docs/app/getting-started/updating-data](https://nextjs.org/docs/app/getting-started/updating-data)

[2] React, "Server Functions," React Docs, 2024. [Online]. Available: [https://react.dev/reference/rsc/server-functions](https://react.dev/reference/rsc/server-functions)

[3] "Next.js Server Actions," YouTube, 2024. [Video]. Available: [https://www.youtube.com/watch?v=gQ2bVQPFS4U](https://www.youtube.com/watch?v=gQ2bVQPFS4U)

[4] Vercel, "How to think about data security in Next.js," Next.js Docs, 2024. [Online]. Available: [https://nextjs.org/docs/app/guides/data-security#data-fetching-approaches](https://nextjs.org/docs/app/guides/data-security#data-fetching-approaches)

```quiz
Q: What is a Server Function?
- A function that runs only in the browser
- An async function marked 'use server' that executes on the server, callable from Client or Server Components
correct: 1
explain: Server Functions run on the server. When called from a Client Component, the call is serialized, sent across the network, executed server-side, and the result returned.

Q: When a Client Component calls a Server Function, what crosses the network?
- The full function body and all its imports
- A reference to the function plus the arguments — the body stays on the server
correct: 1
explain: The client holds a reference, not the function body. The server looks up and executes the function, so server-only code and credentials never reach the browser.

Q: A <form action={createPost}> where createPost is a Server Function…
- requires JavaScript to be enabled to work at all
- works as a standard POST even before hydration (progressive enhancement), then upgrades
correct: 1
explain: The action prop accepts a Server Function and degrades to a regular POST before hydration. After hydration it's intercepted for the enhanced experience.

Q: Why are Server Functions a natural fit for mutations that touch the database?
- They run on the server, so they can call the DB directly without exposing credentials to the browser
- They automatically encrypt the database
correct: 0
explain: Because Server Functions execute server-side, they can call the database, CMS, or any server-only API directly. The credentials stay on the server; only a reference reaches the client.

Q: After a Server Function writes data, what does it typically do before returning?
- Nothing — the cache updates automatically
- Call revalidateTag or revalidatePath to bust the affected cache so the next read serves fresh data
correct: 1
explain: Mutations end with a revalidation call so the cache reflects the change. Without it, the stale cached version keeps being served.
```
