# Server Actions
## Basic Server Action
```tsx
// app/actions.ts
"use server"
import { db } from "@/lib/db"
import { revalidatePath } from "next/cache"
export async function createPost(formData: FormData) {
const title = formData.get("title") as string
const content = formData.get("content") as string
await db.post.create({
data: { title, content },
})
revalidatePath("/posts")
}
```
## Form with Server Action
```tsx
// app/posts/new/page.tsx
import { createPost } from "@/app/actions"
export default function NewPost() {
return (
)
}
```
## Server Action with Validation
```tsx
// app/actions.ts
"use server"
import { z } from "zod"
import { revalidatePath } from "next/cache"
const CreatePostSchema = z.object({
title: z.string().min(3).max(100),
content: z.string().min(10),
})
export async function createPost(formData: FormData) {
const validatedFields = CreatePostSchema.safeParse({
title: formData.get("title"),
content: formData.get("content"),
})
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
}
}
const { title, content } = validatedFields.data
await db.post.create({
data: { title, content },
})
revalidatePath("/posts")
return { success: true }
}
```
## Client Component with Server Action
```tsx
// components/create-post-form.tsx
"use client"
import { createPost } from "@/app/actions"
import { useFormState, useFormStatus } from "react-dom"
const initialState = {
errors: {},
}
function SubmitButton() {
const { pending } = useFormStatus()
return (
)
}
export function CreatePostForm() {
const [state, formAction] = useFormState(createPost, initialState)
return (
)
}
```
## Server Action with Redirect
```tsx
// app/actions.ts
"use server"
import { redirect } from "next/navigation"
import { revalidatePath } from "next/cache"
export async function createPost(formData: FormData) {
const post = await db.post.create({
data: {
title: formData.get("title") as string,
content: formData.get("content") as string,
},
})
revalidatePath("/posts")
redirect(`/posts/${post.id}`)
}
```
## Optimistic Updates
```tsx
// components/todo-list.tsx
"use client"
import { experimental_useOptimistic as useOptimistic } from "react"
import { toggleTodo } from "@/app/actions"
export function TodoList({ todos }: { todos: Todo[] }) {
const [optimisticTodos, addOptimisticTodo] = useOptimistic(todos, (state, newTodo: Todo) => [
...state,
newTodo,
])
async function handleSubmit(formData: FormData) {
const title = formData.get("title") as string
const newTodo = { id: crypto.randomUUID(), title, completed: false }
// Optimistically update UI
addOptimisticTodo(newTodo)
// Send to server
await createTodo(formData)
}
return (
)
}
```
## Server Action with Authentication
```tsx
// app/actions.ts
"use server"
import { auth } from "@/lib/auth"
import { redirect } from "next/navigation"
export async function createPost(formData: FormData) {
const session = await auth()
if (!session) {
redirect("/login")
}
await db.post.create({
data: {
title: formData.get("title") as string,
content: formData.get("content") as string,
authorId: session.user.id,
},
})
revalidatePath("/posts")
}
```
## Inline Server Action
```tsx
// app/posts/page.tsx
import { db } from "@/lib/db"
import { revalidatePath } from "next/cache"
export default async function Posts() {
const posts = await db.post.findMany()
async function deletePost(formData: FormData) {
"use server"
const id = formData.get("id") as string
await db.post.delete({ where: { id } })
revalidatePath("/posts")
}
return (
{posts.map((post) => (
-
{post.title}
))}
)
}
```
## Programmatic Server Action Call
```tsx
// components/delete-button.tsx
"use client"
import { deletePost } from "@/app/actions"
export function DeleteButton({ postId }: { postId: string }) {
async function handleDelete() {
if (confirm("Are you sure?")) {
await deletePost(postId)
}
}
return
}
// app/actions.ts
;("use server")
export async function deletePost(postId: string) {
await db.post.delete({ where: { id: postId } })
revalidatePath("/posts")
}
```
## Revalidation Strategies
```tsx
// app/actions.ts
"use server"
import { revalidatePath, revalidateTag } from "next/cache"
export async function updatePost(id: string, data: UpdatePostData) {
await db.post.update({ where: { id }, data })
// Revalidate specific path
revalidatePath("/posts")
revalidatePath(`/posts/${id}`)
// Revalidate all paths in a layout
revalidatePath("/posts", "layout")
// Revalidate by cache tag
revalidateTag("posts")
}
```
## Server Action with File Upload
```tsx
// app/actions.ts
"use server"
import { writeFile } from "fs/promises"
import { join } from "path"
export async function uploadAvatar(formData: FormData) {
const file = formData.get("avatar") as File
if (!file) {
return { error: "No file uploaded" }
}
const bytes = await file.arrayBuffer()
const buffer = Buffer.from(bytes)
const path = join(process.cwd(), "public", "uploads", file.name)
await writeFile(path, buffer)
return { success: true, path: `/uploads/${file.name}` }
}
// components/upload-form.tsx
;("use client")
import { uploadAvatar } from "@/app/actions"
export function UploadForm() {
async function handleSubmit(formData: FormData) {
const result = await uploadAvatar(formData)
if (result.success) {
console.log("Uploaded to:", result.path)
}
}
return (
)
}
```
## Error Handling
```tsx
// app/actions.ts
"use server"
export async function createPost(formData: FormData) {
try {
await db.post.create({
data: {
title: formData.get("title") as string,
content: formData.get("content") as string,
},
})
revalidatePath("/posts")
return { success: true }
} catch (error) {
console.error("Failed to create post:", error)
return { error: "Failed to create post" }
}
}
// components/form.tsx
;("use client")
export function CreatePostForm() {
const [error, setError] = useState(null)
async function handleSubmit(formData: FormData) {
const result = await createPost(formData)
if (result.error) {
setError(result.error)
} else {
// Success
router.push("/posts")
}
}
return (
)
}
```
## Server Action with Cookies
```tsx
// app/actions.ts
"use server"
import { cookies } from "next/headers"
export async function setTheme(theme: "light" | "dark") {
cookies().set("theme", theme, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
maxAge: 60 * 60 * 24 * 365, // 1 year
path: "/",
})
}
export async function getTheme() {
return cookies().get("theme")?.value ?? "light"
}
```
## Rate Limiting
```tsx
// app/actions.ts
"use server"
import { ratelimit } from "@/lib/redis"
export async function createPost(formData: FormData) {
const session = await auth()
const { success } = await ratelimit.limit(session.user.id)
if (!success) {
return { error: "Rate limit exceeded" }
}
// Create post...
}
```
## Quick Reference
| Capability | Usage |
| ---------------- | --------------------------------------------- |
| **Define** | Add 'use server' at top of file or function |
| **Form** | Pass action to `