Hai thứ giải quyết hai bài toán khác nhau.
Server Action — mutation phát sinh từ chính UI của app:
- Gắn thẳng vào <form action={...}> hoặc gọi từ client component, không phải tự viết fetch và endpoint.
- Chạy được cả khi JS chưa tải (progressive enhancement với form).
- Sau khi ghi xong, gọi revalidatePath/revalidateTag để đồng bộ lại UI trong cùng một vòng.
- Luôn là POST và chạy tuần tự — không hợp cho đường đọc dữ liệu hay gọi song song nhiều lần.
Route Handler — cần một HTTP endpoint thật:
- Bên thứ ba gọi vào: webhook thanh toán, callback OAuth, cron.
- Mobile app hoặc domain khác tiêu thụ (cần CORS, versioning).
- Cần kiểm soát chi tiết method/status/header, trả file, stream, hoặc SSE.
- Cần GET có cache/CDN.
// app/api/webhooks/sepay/route.ts — bắt buộc là route handler
export async function POST(req: Request) {
const sig = req.headers.get('x-signature')
// verify signature, ghi DB
return Response.json({ ok: true })
}Chốt: form và nút bấm trong app dùng Server Action; hệ thống bên ngoài gọi vào thì dùng Route Handler.
Đọc dữ liệu cho trang thì không cần cả hai — fetch thẳng trong Server Component.