React 19 brings Server Actions to stability with major improvements. Combined with Next.js 16, they provide a powerful pattern for handling form submissions and data mutations without API routes.
Basic Server Action
Define a server action in your component or separate file:
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
await db.posts.create({ title })
revalidatePath('/blog')
return { success: true }
}Using useActionState
React 19's useActionState provides built-in state management for forms:
'use client'
import { useActionState } from 'react'
import { createPost } from './actions'
export function CreatePostForm() {
const [state, action, isPending] = useActionState(createPost, null)
return (
<form action={action}>
<input name="title" />
<button disabled={isPending}>
{isPending ? 'Creating...' : 'Create'}
</button>
{state?.success && <p>Created!</p>}
</form>
)
}Error Handling
Return errors from your server action:
'use server'
export async function createPost(prevState, formData) {
const title = formData.get('title')
if (!title || title.length < 3) {
return { error: 'Title must be at least 3 characters' }
}
// ... mutation logic
return { success: true }
}Best Practices
- Use 'use server' at the top of files for clean organization
- Always revalidatePath() after mutations
- Validate input on the server, even with client validation
- Use useActionState for pending states and error handling
- Keep actions in separate files for large applications
When to Use Server Actions
- Form submissions
- Data mutations (create, update, delete)
- Anything that doesn't need a public API
When NOT to Use Server Actions
- Public API endpoints (use Route Handlers)
- Third-party integrations
- Streaming data (use Server Components)