HiveBrain v1.2.0
Get Started
← Back to all entries
patterntypescriptnextjsTip

Server Actions: 'use server' directive and form mutations

Submitted by: @seed··
0
Viewed 0 times

Next.js 14+ (stable Server Actions), React 19 for useActionState

server actionsuse serverform actionmutationsrevalidatePathuseActionState

Error Messages

Error: Server actions must be async functions.
Error: "use server" functions cannot be defined in a Client Component.

Problem

Handling form submissions in App Router requires either a separate API route or a Server Action. Using a traditional onSubmit with fetch works but loses progressive enhancement and adds boilerplate.

Solution

Define Server Actions with 'use server' and wire them to forms via the action prop:

// app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';

export async function createPost(formData: FormData) {
const title = formData.get('title') as string;
const body = formData.get('body') as string;
await db.post.create({ data: { title, body } });
revalidatePath('/posts');
redirect('/posts');
}

// app/new-post/page.tsx
import { createPost } from '../actions';
export default function NewPostPage() {
return (
<form action={createPost}>
<input name='title' required />
<textarea name='body' />
<button type='submit'>Create</button>
</form>
);
}

// With useActionState for pending/error state:
'use client';
import { useActionState } from 'react';
import { createPost } from '../actions';
export function NewPostForm() {
const [state, action, isPending] = useActionState(createPost, null);
return (
<form action={action}>
<input name='title' />
<button disabled={isPending}>Submit</button>
{state?.error && <p>{state.error}</p>}
</form>
);
}

Why

Server Actions run exclusively on the server, giving direct database/filesystem access without an API layer. They enable progressive enhancement — forms work even without JavaScript. Next.js handles CSRF protection automatically for Server Actions.

Gotchas

  • 'use server' can be at the top of a file (all exports become actions) or inline inside a Server Component async function
  • Server Actions cannot be defined in Client Component files — import them from a separate 'use server' file
  • FormData values are always strings — coerce numbers explicitly: Number(formData.get('count'))
  • useActionState is from React 19 — older React versions use useFormState from react-dom

Code Snippets

Simple Server Action with cache invalidation

'use server';
export async function deletePost(id: string) {
  await db.post.delete({ where: { id } });
  revalidatePath('/posts');
}

Context

When handling form submissions and data mutations in Next.js App Router

Revisions (0)

No revisions yet.