'use server'

Marks a function or a whole file as server-only, turning its exports into callable Server Functions.

Signature
'use server'
  • Added in Next.js v13.4.0

Errors

Exporting a non-async value from a file that begins with the directive is a build error, as is placing the directive in a module that also declares 'use client'.

Changelog

  • PHP v14.0.0Server Actions became stable and enabled by default; earlier versions required experimental.serverActions: true in next.config.js.
  • PHP v13.4.0Directive introduced alongside the App Router's Server Actions (alpha).

'use server' marks code that must run on the server. From the client's point of view it replaces the function body with a reference — an action ID plus a dispatcher — that POSTs back to the route that rendered it. The implementation never reaches the browser bundle.

There are two placements. On the first line of a file, every export in that file becomes a Server Function. As the first statement inside an async function, only that function becomes one, and it can close over values the surrounding Server Component already fetched.

When to reach for it

  • A Client Component needs to write to the database, so the write lives in a shared app/actions.ts and is imported.
  • A Server Component needs a one-off mutation bound to the row it just rendered — use the inline form.

What trips people up

Every export of a file-level module must be async; a sync helper exported from the same file fails the build, so move helpers elsewhere. Arguments and return values are serialized across the network, so class instances and functions do not survive, and returning a raw database row leaks every column to the client. Rendering the form behind an auth check guards the UI, not the endpoint — re-read the session inside the function itself.

Example

Example · typescript
'use server'

import { db } from '@/lib/db'
import { auth } from '@/lib/auth'
import { revalidatePath } from 'next/cache'

export async function createPost(formData: FormData) {
  const session = await auth()
  if (!session?.user) {
    throw new Error('Unauthorized')
  }

  const title = String(formData.get('title') ?? '').trim()
  if (title.length === 0) {
    return { error: 'Title is required' }
  }

  const post = await db.post.create({
    data: { title, authorId: session.user.id },
  })

  revalidatePath('/posts')

  // Return only what the UI renders, never the raw row.
  return { id: post.id }
}

When to use it

  • Handle a signup form submission and write the user row without an API route.
  • Delete a record from a button inside a Client Component list.
  • Revalidate a cached path immediately after a mutation, in the same request.

Frequently asked questions

Do I need 'use server' on every file that talks to my database?

No — only on files whose functions the client is allowed to call. A Server Component or a plain module imported by one already runs on the server; adding the directive there would needlessly publish those functions as POST endpoints.

Why do I get "Only async functions are allowed to be exported in a 'use server' file"?

A file-level directive requires every export to be an async function, so an exported constant, type helper, or synchronous utility in the same file breaks the build. Move the non-action exports into a separate module and import them.

Can I call a Server Function from a useEffect or an onClick?

Yes — import it into a Client Component and call it, wrapping the call in startTransition so React treats it as an Action and pending state works. It does not have to be attached to a form.

More examples

Inline directive inside a Server Component

The inline form keeps the action next to the data that produced it. `id` comes from the render, not from the submitted form, so the client cannot swap it for another post — but the closure is still round-tripped through the browser, so the save path must authorize the user itself.

Example · typescript
// app/posts/[id]/page.tsx
import { EditPost } from './edit-post'
import { revalidatePath } from 'next/cache'

export default async function PostPage({ params }: PageProps<'/posts/[id]'>) {
  const { id } = await params
  const post = await getPost(id)

  async function updatePost(formData: FormData) {
    'use server'
    // `id` is closed over. Next.js encrypts closed-over variables before
    // sending them to the client, but still verify auth before saving.
    await savePost(id, formData)
    revalidatePath(`/posts/${id}`)
  }

  return <EditPost updatePostAction={updatePost} post={post} />
}

Importing a Server Function into a Client Component

The client module imports a reference, not the implementation — `db` and `auth` never appear in the browser bundle. Calling it outside an event handler's transition works, but wrapping it in `startTransition` is what lets React track it as an Action and drive pending UI.

Example · typescript
// app/actions.ts
'use server'

import { db } from '@/lib/db'
import { auth } from '@/lib/auth'

export async function fetchUsers() {
  const session = await auth()
  if (!session?.user) {
    throw new Error('Unauthorized')
  }

  return db.user.findMany({
    select: { id: true, name: true, email: true },
  })
}

// app/components/my-button.tsx
'use client'

import { startTransition } from 'react'
import { fetchUsers } from '../actions'

export default function MyButton() {
  return (
    <button
      onClick={() =>
        startTransition(async () => {
          const users = await fetchUsers()
          console.log(users.length)
        })
      }
    >
      Fetch Users
    </button>
  )
}

The two directives cannot share a module

The directives declare opposite sides of the same boundary, so one module cannot claim both. The fix is always the same shape: actions in their own `'use server'` module, the interactive markup in a `'use client'` module that imports it.

Example · typescript
// app/bad.tsx — this file will not build
'use client'
'use server' // Error: cannot use both directives in the same file

export async function save(formData: FormData) {}

// Correct: two files, one boundary each.
// app/actions.ts
'use server'
export async function save(formData: FormData) {
  /* ... */
}

// app/form.tsx
'use client'
import { save } from './actions'

export function Form() {
  return <form action={save}>{/* ... */}</form>
}

Discussion

  • Be the first to comment on this lesson.