Server Actions
Server Actions are async functions that run on the server but can be called from Client Components. They're the modern way to handle mutations in Next.js.
Defining Server Actions
Add 'use server' to mark a function as a Server Action:
// In a separate file (recommended)
// app/actions.ts
'use server'
export async function createPost(formData: FormData) {
const title = formData.get('title') as string
const content = formData.get('content') as string
await db.posts.create({
data: { title, content }
})
}
Or inline in a Server Component:
// app/page.tsx
export default function Page() {
async function submitForm(formData: FormData) {
'use server'
// This runs on the server
await db.save(formData.get('email'))
}
return (
<form action={submitForm}>
<input name="email" type="email" />
<button type="submit">Subscribe</button>
</form>
)
}
With Forms (Progressive Enhancement)
Server Actions work without JavaScript:
// app/contact/page.tsx
import { submitContact } from '@/app/actions'
export default function ContactPage() {
return (
<form action={submitContact}>
<input name="name" required />
<input name="email" type="email" required />
<textarea name="message" required />
<button type="submit">Send</button>
</form>
)
}
// app/actions.ts
'use server'
export async function submitContact(formData: FormData) {
const name = formData.get('name') as string
const email = formData.get('email') as string
const message = formData.get('message') as string
await db.contacts.create({
data: { name, email, message }
})
}
From Client Components
Import Server Actions into Client Components:
// app/actions.ts
'use server'
export async function addToCart(productId: string) {
const cart = await getCart()
await cart.add(productId)
}
// components/AddToCartButton.tsx
'use client'
import { addToCart } from '@/app/actions'
export function AddToCartButton({ productId }: { productId: string }) {
return (
<button onClick={() => addToCart(productId)}>
Add to Cart
</button>
)
}
Revalidation
Update cached data after mutations:
'use server'
import { revalidatePath, revalidateTag } from 'next/cache'
export async function createPost(formData: FormData) {
await db.posts.create({ ... })
// Revalidate specific path
revalidatePath('/posts')
// Or revalidate by tag
revalidateTag('posts')
}
Handling Loading and Errors
With useFormStatus
'use client'
import { useFormStatus } from 'react-dom'
function SubmitButton() {
const { pending } = useFormStatus()
return (
<button type="submit" disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
)
}
With useActionState
'use client'
import { useActionState } from 'react'
import { createPost } from '@/app/actions'
export function PostForm() {
const [state, formAction, pending] = useActionState(createPost, null)
return (
<form action={formAction}>
<input name="title" />
{state?.error && <p className="error">{state.error}</p>}
<button disabled={pending}>
{pending ? 'Creating...' : 'Create Post'}
</button>
</form>
)
}
Returning Data
Return data from Server Actions:
'use server'
export async function createPost(formData: FormData) {
try {
const post = await db.posts.create({ ... })
return { success: true, post }
} catch (error) {
return { success: false, error: 'Failed to create post' }
}
}
'use client'
import { useActionState } from 'react'
import { createPost } from '@/app/actions'
export function PostForm() {
const [state, formAction] = useActionState(createPost, null)
return (
<form action={formAction}>
<input name="title" />
{state?.success && <p>Post created: {state.post.id}</p>}
{state?.error && <p className="error">{state.error}</p>}
<button>Create</button>
</form>
)
}
Redirecting
Redirect after a mutation:
'use server'
import { redirect } from 'next/navigation'
export async function createPost(formData: FormData) {
const post = await db.posts.create({ ... })
redirect(`/posts/${post.id}`)
}
Summary
- Use
'use server'to define Server Actions - They work with forms for progressive enhancement
- Can be called from Client Components like regular functions
- Use
revalidatePathorrevalidateTagto update cached data useFormStatusprovides loading statesuseActionStatemanages form state and errors- Use
redirectfor post-mutation navigation

