Actions vs route handlers
Server Actions for your own UI, Route Handlers for everyone else.
From next-best-practices, route-handlers.md.
Sort the use cases
Drag each card into a bin, or click to cycle.
Server Action
Route Handler
Both work
0 of 8 sorted
// app/posts/new/page.tsx
import { createPost } from './actions'
export default function NewPost() {
return (
<form action={createPost}>
<input name="title" />
<button>Publish</button>
</form>
)
}
// app/api/webhooks/stripe/route.ts
export async function POST(request: Request) {
const event = await request.json()
await handleStripeEvent(event)
return Response.json({ received: true })
}
Turn JavaScript off
Pick a JavaScript state, then publish on both sides.
fetch() in onClickBad<button onClick={() => fetch(…)}><form action>Good'use server'// app/posts/new/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost(formData: FormData) {
const title = String(formData.get('title') ?? '')
await db.post.create({ data: { title } })
revalidatePath('/posts')
}
// submit-button.tsx
'use client'
import { useFormStatus } from 'react-dom'
export function SubmitButton() {
const { pending } = useFormStatus()
return <button disabled={pending}>{pending ? 'Publishing…' : 'Publish'}</button>
}
Put route.ts next to page.tsx
Add the route, then move it. Try a request.
File treeClean
Request
GET POST PUT PATCH DELETE HEAD OPTIONSapp/
├── users/
│ └── page.tsx # /users (page)
└── api/
└── users/
└── route.ts # /api/users (API)
Why it matters, pros and cons
Why it matters
An Action is a function your UI calls; a Route Handler is a public URL.
Pros
- No hand-written endpoint for your own mutations.
- Forms submit before JavaScript loads.
- Route Handler GETs can be HTTP-cached.
Cons
- Actions are POST only, so no HTTP caching.
- No stable URL for webhooks or mobile apps.
- Actions are public endpoints; authenticate inside them.