With Server Functions (and Server Actions), you can mutate data directly on the server
Because it’s invoked over a network request, it must be async.
✅ Server Action = Server Function used for form submissions & mutations.
✅ Server Function = The broader concept
You define a Server Function using the "use server" directive.
You can:
// Option 1: Inside a Functionexport async function createPost(formData: FormData) {'use server'const title = formData.get('title')const content = formData.get('content')// Update database// Revalidate cache}// Option 2: At the Top of a File//This marks every exported function in that file as a Server Function.'use server'export async function createPost() {}export async function deletePost() {}
You can inline them directly inside a Server Component:
export default function Page() {async function createPost(formData: FormData) {'use server'// ...}return <></>}
But you can import and call them.
'use client'import { createPost } from '@/app/actions'export function Button() {return <button formAction={createPost}>Create</button>}
In Client Components, forms invoking Server Actions will queue submissions if JavaScript isn’t loaded yet, and will be prioritized for hydration. After hydration, the browser does not refresh on form submission.
<ClientComponent updateItemAction={updateItem} />
'use client'export default function ClientComponent({updateItemAction,}: {updateItemAction: (formData: FormData) => void}) {return <form action={updateItemAction}>{/* ... */}</form>}
There are two main ways:
React extends the <form> element to accept Server Functions.
import { createPost } from '@/app/actions'export function Form() {return (<form action={createPost}><input type="text" name="title" /><input type="text" name="content" /><button type="submit">Create</button></form>)}
The function automatically receives FormData.
'use server'export async function createPost(formData: FormData) {const title = formData.get('title')const content = formData.get('content')// Update DB// Revalidate}
You can call them inside onClick:
'use client'import { incrementLike } from './actions'import { useState } from 'react'export default function LikeButton({ initialLikes }) {const [likes, setLikes] = useState(initialLikes)return (<><p>Total Likes: {likes}</p><buttononClick={async () => {const updatedLikes = await incrementLike()setLikes(updatedLikes)}}>Like</button></>)}
⚠️ Server Functions are executed one at a time from the client.
If you need parallel work, do it inside one Server Function.
Use useActionState:
'use client'import { useActionState, startTransition } from 'react'import { createPost } from '@/app/actions'export function Button() {const [state, action, pending] = useActionState(createPost, false)return (<button onClick={() => startTransition(action)}>{pending ? 'Loading...' : 'Create Post'}</button>)}
This gives you:
pending boolean.
refresh()re-renders the current route.
It does NOT revalidate tagged data
If you need to refresh the current route:
'use server'import { refresh } from 'next/cache'export async function updatePost(formData: FormData) {// Update DBrefresh()}
To revalidate specific data:
import { revalidatePath } from 'next/cache'export async function createPost(formData: FormData) {'use server'// Update DBrevalidatePath('/posts')}