# Next.js 16 Server Actions ปี 2026: Mutation, Revalidation และคำถามสัมภาษณ์
> Next.js 16 Server Actions จัดการ mutation, revalidation, สถานะ pending, optimistic UI และความปลอดภัยอย่างไร พร้อมคำถามสัมภาษณ์ที่ทดสอบแต่ละแนวคิด
- Published: 2026-06-24
- Updated: 2026-07-06
- Author: SharpSkill
- Tags: next.js, server-actions, react, mutations, revalidation, interview
- Reading time: 11 min
---
Next.js Server Actions เปลี่ยนฟังก์ชัน async ธรรมดาให้กลายเป็น endpoint สำหรับการเปลี่ยนแปลงข้อมูล (mutation) ฝั่งเซิร์ฟเวอร์ที่ฟอร์มเรียกใช้ได้โดยตรง โดยไม่ต้องมี API route ไม่ต้องใช้ `fetch` ฝั่งไคลเอนต์ และไม่ต้องทำ serialization ของ JSON เอง ใน Next.js 16 นี่คือวิธีมาตรฐานสำหรับการเขียน mutation ของข้อมูล และปรากฏในการสัมภาษณ์งาน React ระดับ senior แทบทุกครั้งในปี 2026 บทวิเคราะห์เชิงลึกนี้อธิบายว่ามันทำงานเบื้องหลังอย่างไร การ revalidation ส่งข้อมูลใหม่ไปยัง UI ได้อย่างไร วิธีจัดการสถานะ pending และ error รวมถึงกฎด้านความปลอดภัยที่ทำให้นักพัฒนาส่วนใหญ่สะดุด
> **นิยามในหนึ่งประโยค**
>
> Server Action คือฟังก์ชัน async ที่กำกับด้วย `"use server"` ซึ่งทำงานเฉพาะบนเซิร์ฟเวอร์ และเรียกใช้จาก client component หรือจากฟอร์มได้ราวกับเป็นฟังก์ชันภายในเครื่อง ในขณะที่ Next.js จัดการ request ผ่านเครือข่าย การ serialize อาร์กิวเมนต์ และการป้องกัน CSRF ให้โดยอัตโนมัติ
## Server Action ทำงานเบื้องหลังอย่างไร
Server Action คือฟังก์ชันที่ทำงานเฉพาะฝั่งเซิร์ฟเวอร์ ซึ่งถูกเรียกผ่านเครือข่ายด้วย endpoint ที่คอมไพเลอร์สร้างขึ้น เมื่อฟังก์ชันมี directive `"use server"` คอมไพเลอร์ของ Next.js จะไม่ส่งเนื้อหาของฟังก์ชันไปยังเบราว์เซอร์เลย แต่จะแทนที่ฟังก์ชันด้วยการอ้างอิงแบบเบา นั่นคือ action ID ที่ถูกแฮชและแมปไปยัง POST route ที่ถูกสร้างขึ้น เมื่อเรียก action จากไคลเอนต์ ระบบจะส่ง request ไปยัง route นั้น ฟังก์ชันจริงจะทำงานบนเซิร์ฟเวอร์ และผลลัพธ์ที่ผ่านการ serialize จะสตรีมกลับมายังผู้เรียก
การออกแบบนี้มีผลลัพธ์สามประการที่ควรจดจำทั้งสำหรับโค้ดที่ใช้งานจริงและสำหรับการสัมภาษณ์ ประการแรก อาร์กิวเมนต์และค่าที่ส่งคืนต้อง serialize ได้ เพราะมันเดินทางข้ามเครือข่ายในรูปแบบ payload ที่ถูกเข้ารหัส ประการที่สอง ความลับต่าง ๆ ยังคงอยู่บนเซิร์ฟเวอร์ เนื่องจากเนื้อหาของฟังก์ชันไม่เคยถูกรวมเข้าไปใน bundle ของไคลเอนต์ ประการที่สาม action ที่ผูกกับ `
)
}
```
แอตทริบิวต์ `name` ของแต่ละฟิลด์จะกลายเป็นคีย์ที่ `formData.get()` อ่านได้ [เอกสารอ้างอิง FormData ของ MDN](https://developer.mozilla.org/en-US/docs/Web/API/FormData) อธิบายว่าเบราว์เซอร์ประกอบออบเจ็กต์นี้จากฟอร์มอย่างไร หากต้องการควบคุมที่ละเอียดขึ้น `next/form` ขยายความสามารถของ element แบบเนทีฟด้วยการนำทางฝั่งไคลเอนต์และการ prefetch ในขณะที่ยังคงสัญญาการทำงานแบบ action เดิมไว้
## จัดการสถานะ Pending และ Error ด้วย useActionState
ฟอร์มในโลกจริงต้องการทั้งการแสดงผลการตรวจสอบความถูกต้องและตัวบ่งชี้การโหลด hook `useActionState` ของ React 19 ห่อ Server Action ร้อยค่าสถานะผ่านการ submit แต่ละครั้ง และเปิดเผยแฟล็ก `isPending` มันมาแทนที่ชื่อเดิม `useFormState` และคืนค่าเป็น tuple ของสถานะปัจจุบัน action ที่ถูกห่อ และค่าบูลีน pending
```tsx
// app/posts/new-post-form.tsx
"use client"
import { useActionState } from "react"
import { createPost } from "./actions"
const initialState = { error: "" }
export default function NewPostForm() {
// state carries the last return value; isPending tracks the in-flight request
const [state, formAction, isPending] = useActionState(createPost, initialState)
return (
)
}
```
เมื่อ hook เป็นตัวขับเคลื่อน action ลายเซ็นของฟังก์ชันจะเพิ่มพารามิเตอร์แรก `prevState` ไว้ก่อน `FormData` การคืนค่าเป็นออบเจ็กต์แทนการ throw ช่วยให้คอมโพเนนต์เรนเดอร์การตรวจสอบความถูกต้องแบบ inline ได้โดยไม่ต้องใช้ error boundary
```typescript
// app/posts/actions.ts
"use server"
import { revalidateTag } from "next/cache"
import { PostService } from "@/lib/services/post-service"
type FormState = { error: string }
export async function createPost(
prevState: FormState,
formData: FormData,
): Promise {
const title = formData.get("title") as string
// Validate on the server; client validation is never trustworthy alone
if (!title || title.length < 3) {
return { error: "Title must be at least 3 characters." }
}
await PostService.create({ title })
// Invalidate by tag rather than path for finer-grained control
revalidateTag("posts")
return { error: "" }
}
```
[เอกสารอ้างอิง useActionState บน react.dev](https://react.dev/reference/react/useActionState) ครอบคลุมรูปแบบขั้นสูง เช่น การรองรับ permalink สำหรับการ submit ก่อน hydration การคืนค่าสถานะแบบมีโครงสร้างช่วยให้เส้นทางปกติ (happy path) และเส้นทาง error อยู่ในที่เดียวกันอย่างคาดเดาได้
## revalidatePath กับ revalidateTag: เลือกวิธี Invalidation ที่ถูกต้อง
mutation เป็นเพียงครึ่งหนึ่งของงาน UI ต้องสะท้อนการเปลี่ยนแปลงนั้นด้วย Next.js เปิดเผยฟังก์ชัน invalidation สองตัวจาก `next/cache` และการเลือกตัวที่ถูกต้องคือคำถามที่ใช้แยกแยะผู้สมัครในการสัมภาษณ์บ่อยครั้ง
| ฟังก์ชัน | ล้างแคชอะไร | เหมาะเมื่อ |
|----------|-------------|-----------|
| `revalidatePath("/posts")` | ทุกรายการแคชของ route หนึ่ง ๆ | mutation ส่งผลต่อหน้าเดียวหรือ layout ที่ชัดเจน |
| `revalidateTag("posts")` | ทุก fetch ที่แคชและติดป้าย (tag) นั้น | ข้อมูลชุดเดียวกันปรากฏในหลาย route |
`revalidatePath` เป็นแบบหยาบและยึด route เป็นศูนย์กลาง มันล้างแคชของ URL หนึ่งและ layout tree ของ URL นั้น ส่วน `revalidateTag` เป็นแบบละเอียดและยึดข้อมูลเป็นศูนย์กลาง `fetch` หรือฟังก์ชันที่แคชใด ๆ ที่ติดป้ายด้วยสตริงนั้นจะถูกล้างไม่ว่ามันจะอยู่ที่ใด การใช้ tag ขยายตัวได้ดีกว่าในแอปขนาดใหญ่ เพราะ mutation เดียวสามารถรีเฟรช sidebar รายการ และหน้ารายละเอียดได้ในการเรียกเพียงครั้งเดียว ฟังก์ชันทั้งสองทำเครื่องหมายว่าข้อมูล "เก่า" (stale) แทนที่จะ refetch ทันที ดังนั้น request ครั้งถัดไปจึงจะสร้างเนื้อหาขึ้นใหม่
ด้วย Cache Components ใน Next.js 16 การติด tag ทำงานร่วมกับ directive `"use cache"` ผ่าน `cacheTag` ซึ่งเปลี่ยนวิธีคิดเกี่ยวกับการแคชทั่วทั้งแอป ปฏิสัมพันธ์นี้อธิบายไว้อย่างละเอียดใน [คู่มือ Cache Components ของ Next.js 16](/blog/react-next/nextjs-16-cache-components-use-cache-ppr-interview-questions) บน SharpSkill
## อัปเดต UI แบบ Optimistic ด้วย useOptimistic
การเดินทางไป-กลับผ่านเครือข่ายเพิ่มความหน่วงที่ผู้ใช้รับรู้ได้ hook `useOptimistic` เรนเดอร์ผลลัพธ์ที่คาดหวังทันที แล้วจึงปรับให้ตรงกับการตอบกลับของเซิร์ฟเวอร์เมื่อ action เสร็จสิ้น มันรับสถานะปัจจุบันและ reducer ที่สร้างเวอร์ชัน optimistic ขึ้นมา
```tsx
// app/posts/like-button.tsx
"use client"
import { useOptimistic } from "react"
import { likePost } from "./actions"
export function LikeButton({ postId, likes }: { postId: string; likes: number }) {
// optimisticLikes updates before the server confirms
const [optimisticLikes, addOptimisticLike] = useOptimistic(
likes,
(current) => current + 1,
)
async function handleLike() {
addOptimisticLike(null) // update the UI immediately
await likePost(postId) // then run the real mutation
}
return (
)
}
```
หาก action ล้มเหลว React จะย้อนค่า optimistic กลับไปเป็นสถานะที่เซิร์ฟเวอร์ยืนยันโดยอัตโนมัติ จึงไม่จำเป็นต้อง rollback เอง [เอกสารอ้างอิง useOptimistic บน react.dev](https://react.dev/reference/react/useOptimistic) อธิบายรายละเอียดว่าวงจรการปรับให้ตรงกัน (reconciliation) ทำงานร่วมกับ transition อย่างไร รูปแบบนี้คือสิ่งที่ทำให้ฟอร์มแบบ Server Action รู้สึกตอบสนองได้ดีเทียบเท่ากับ SPA ที่เรนเดอร์ฝั่งไคลเอนต์ทั้งหมด
## ผูกอาร์กิวเมนต์และเรียก Action จาก Event Handler
ไม่ใช่ทุก mutation ที่มาจากการ submit ฟอร์ม การลบแถว การสลับการตั้งค่า หรือการจัดเรียงรายการใหม่ มักเกิดจากการคลิกปุ่มหรือ event อื่น ๆ Server Action เป็นการอ้างอิงฟังก์ชันปกติบนไคลเอนต์ จึงเรียกใช้จาก handler ใดก็ได้ ตราบใดที่การเรียกนั้นทำงานภายใน transition เพื่อรักษาให้ UI ตอบสนองอยู่เสมอ
อาร์กิวเมนต์เพิ่มเติมที่ไม่ใช่ฟิลด์ของฟอร์มจะถูกแนบด้วย `bind` ซึ่งสร้าง action ใหม่ที่มีค่าเหล่านั้นนำหน้า นี่คือวิธีที่เป็นสำนวนมาตรฐานในการส่ง ID ควบคู่ไปกับ `FormData` ที่ถูก submit
```tsx
// app/posts/post-actions.tsx
"use client"
import { useTransition } from "react"
import { deletePost, updatePost } from "./actions"
export function PostActions({ postId }: { postId: string }) {
const [isPending, startTransition] = useTransition()
// Bind the id so the action receives it before FormData
const updateWithId = updatePost.bind(null, postId)
return (
{/* Event-handler invocation wrapped in a transition */}
)
}
```
อาร์กิวเมนต์ที่ผูกไว้จะถูกเข้ารหัสเช่นเดียวกับค่าอื่น ๆ ที่ถูกปิดล้อม (closed-over) ดังนั้น `postId` จึงไม่ถูกดัดแปลงระหว่างการส่ง แม้ว่า action ยังคงต้องยืนยันว่าผู้เรียกมีสิทธิ์กระทำต่อเรกคอร์ดนั้นก็ตาม การห่อการเรียกโดยตรงไว้ใน `startTransition` ช่วยให้ React รักษาให้ส่วนติดต่อยังโต้ตอบได้ และแสดงสถานะ pending ออกมาโดยไม่ต้องมีฟอร์มห่อ
## ความปลอดภัยของ Server Actions: ปฏิบัติต่อทุก Action เสมือนเป็น API สาธารณะ
ความเข้าใจผิดที่อันตรายที่สุดคือการคิดว่า Server Action เป็นสิ่งที่เป็นส่วนตัวเพราะมันถูกเขียนอยู่ข้าง ๆ โค้ดเซิร์ฟเวอร์ ในความเป็นจริง เมื่อ action ถูกนำไปใช้ Next.js จะสร้าง POST endpoint สาธารณะที่ใครก็ตามสามารถเรียกด้วย request ที่ประดิษฐ์ขึ้นได้ เฟรมเวิร์กจัดหา action ID ที่ปลอดภัยและกำจัด action ที่ไม่ได้ใช้ออกระหว่าง `next build` แต่มันไม่ได้ตรวจสอบสิทธิ์ของผู้เรียกให้
> **ตรวจสอบสิทธิ์ภายในทุก Action**
>
> Server Actions คือ endpoint HTTP ที่เข้าถึงได้ การตรวจสอบ session ใน layout หรือ middleware ไม่สามารถปกป้องมันได้ เพราะผู้โจมตีสามารถ POST ไปยัง action ได้โดยตรง จงตรวจสอบ session และสิทธิ์ของผู้เรียกภายในตัว action ก่อนที่จะแตะต้องข้อมูลใด ๆ
```typescript
// app/admin/actions.ts
"use server"
import { auth } from "@/lib/auth"
import { headers } from "next/headers"
import { UserService } from "@/lib/services/user-service"
export async function deleteUser(userId: string) {
// Authenticate on every invocation, not in a wrapper component
const session = await auth.api.getSession({ headers: await headers() })
if (!session || session.user.role !== "admin") {
throw new Error("Unauthorized")
}
await UserService.delete(userId)
}
```
ยังมีอีกสองกฎที่สำคัญ ค่าที่ถูกปิดล้อม (closed-over) โดย action แบบ inline จะถูกเข้ารหัสก่อนส่งไปยังไคลเอนต์และถอดรหัสบนเซิร์ฟเวอร์ ดังนั้นโทเค็นที่ถูกจับไว้จึงไม่รั่วไหลในรูปข้อความธรรมดา แต่การพึ่งพาสิ่งนี้เปราะบาง จงเก็บความลับให้พ้นจาก closure โดยสิ้นเชิง และอาร์กิวเมนต์ใด ๆ ที่มาจากไคลเอนต์คืออินพุตที่ไม่น่าเชื่อถือซึ่งต้องผ่านการตรวจสอบความถูกต้อง เช่นเดียวกับ body ของ REST [คู่มือความปลอดภัยของข้อมูลใน Next.js](https://nextjs.org/docs/app/guides/data-security) กำหนดโมเดล taint ที่อยู่เบื้องหลังการรับประกันเหล่านี้อย่างเป็นทางการ สำหรับการฝึกฝนอย่างมีโครงสร้างเกี่ยวกับการแลกเปลี่ยนเหล่านี้ [โมดูลคำถามสัมภาษณ์ Next.js Server Actions](/technologies/react-next/interview-questions/nextjs-server-actions) บน SharpSkill เจาะลึกคำถามที่คณะกรรมการสัมภาษณ์ถามจริง
## คำถามสัมภาษณ์เรื่อง Server Actions ของ Next.js 16
คำถามเหล่านี้ปรากฏซ้ำ ๆ ในรอบสัมภาษณ์ React และ full-stack ระดับ senior ปี 2026
**เหตุใด Server Action จึงรับและคืนค่าได้เฉพาะค่าที่ serialize ได้เท่านั้น** เพราะอาร์กิวเมนต์และผลลัพธ์เดินทางข้ามขอบเขตเครือข่ายระหว่างไคลเอนต์กับเซิร์ฟเวอร์ในรูปแบบ payload ที่ถูกเข้ารหัส ฟังก์ชัน อินสแตนซ์ของคลาส และ DOM node ไม่สามารถ serialize ได้ การส่งมันจึงทำให้เกิด error ส่วน `FormData` ออบเจ็กต์ธรรมดา อาร์เรย์ และค่า primitive นั้นปลอดภัย
**progressive enhancement ทำงานร่วมกับ Server Actions อย่างไร** เมื่อ action ถูกส่งตรงไปยัง prop `action` ของฟอร์ม เบราว์เซอร์จะ submit ฟอร์มแบบเนทีฟผ่าน request แบบ POST แม้ก่อนที่ React จะ hydrate Next.js จะดักจับและรัน action ฝั่งเซิร์ฟเวอร์ ดังนั้นฟอร์มจึงทำงานได้โดยไม่ต้องใช้ JavaScript ฝั่งไคลเอนต์ และยกระดับเป็นประสบการณ์ที่ขับเคลื่อนด้วยไคลเอนต์หลังการ hydration
**ความแตกต่างระหว่างการ throw กับการคืนค่า error ใน action คืออะไร** การ throw จะส่งต่อไปยัง error boundary ที่ใกล้ที่สุด และเหมาะกับความล้มเหลวที่ไม่คาดคิด ส่วนการคืนค่าออบเจ็กต์สถานะที่มีโครงสร้างผ่าน `useActionState` เหมาะกับ error การตรวจสอบความถูกต้องที่คาดหมายได้ ซึ่งควรแสดงผลแบบ inline โดยไม่ต้อง unmount ฟอร์ม
**การเรียก revalidatePath refetch ข้อมูลทันทีหรือไม่** ไม่ มันทำเครื่องหมายรายการแคชว่าเก่า (stale) เพื่อให้ request ครั้งถัดไปสร้างมันขึ้นใหม่ การตอบกลับปัจจุบันไม่ได้รับผลกระทบ ซึ่งเป็นเหตุผลว่าทำไมการ redirect หลัง mutation จึงไปตกที่เนื้อหาที่เพิ่งเรนเดอร์ใหม่
สำหรับบริบทการเรนเดอร์ที่กว้างขึ้นซึ่ง mutation เหล่านี้ทำงานอยู่ภายใน บทวิเคราะห์ [React 19 Server Components ในการใช้งานจริง](/blog/react-next/react-19-server-components-production) เข้าคู่กันอย่างเป็นธรรมชาติกับความรู้เรื่อง Server Actions
## บทสรุป
Server Actions ของ Next.js 16 รวมการทำ mutation การ revalidation และ progressive enhancement เข้าไว้ในไพรมิทีฟที่ยึดเซิร์ฟเวอร์เป็นหลักเพียงหนึ่งเดียว ประเด็นสำคัญมีดังนี้
- กำกับ mutation ด้วย `"use server"` และส่งมันไปยัง prop `action` ของฟอร์ม เพื่อให้ได้การจัดการเครือข่ายและ progressive enhancement มาฟรี ๆ
- ขับเคลื่อนสถานะ pending และการตรวจสอบความถูกต้องด้วย `useActionState` โดยคืนค่าออบเจ็กต์ที่มีโครงสร้างสำหรับ error ที่คาดหมายได้แทนการ throw
- เลือกใช้ `revalidatePath` เมื่อมี route เดียวเปลี่ยนแปลง และ `revalidateTag` เมื่อข้อมูลชุดเดียวกันครอบคลุมหลาย route
- ใช้ `useOptimistic` เพื่อเรนเดอร์ผลลัพธ์ที่คาดหวังทันที และปล่อยให้ React ปรับให้ตรงกันหรือย้อนกลับโดยอัตโนมัติ
- ตรวจสอบสิทธิ์และความถูกต้องภายในตัว action ทุกตัว เพราะ action ที่ถูกใช้แต่ละตัวคือ endpoint HTTP สาธารณะที่ middleware เพียงอย่างเดียวไม่สามารถปกป้องได้
---
Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack.
HTML version of this page: https://sharpskill.dev/th/blog/react-next/nextjs-16-server-actions-mutations-revalidation-interview-questions