Xử lý Form trong Next.js 14: Server Actions và Client Components

Table of Contents
Trong hệ sinh thái Next.js đang phát triển nhanh chóng, việc xử lý form đã chứng kiến một sự thay đổi mô hình sâu sắc. Với việc phát hành ổn định Next.js 14, những ngày phải viết các trình xử lý form phía client dài dòng, quản lý quá nhiều trạng thái tải và vật lộn với các API route truyền thống đã qua. Server Actions, kết hợp với các hook hiện đại của React (useFormState và useFormStatus), mang đến một cách tiếp cận được tích hợp sâu sắc, được cải tiến dần dần để xây dựng các form mạnh mẽ.
Khi được kết hợp với các thư viện xác thực schema như Zod, các nhà phát triển giờ đây có thể thực thi an toàn kiểu dữ liệu nghiêm ngặt trên ranh giới client-server. Trong hướng dẫn toàn diện này, chúng ta sẽ khám phá cách xử lý form trong Next.js 14, so sánh Client Components truyền thống với cách tiếp cận Server Actions hiện đại.
Mô hình cũ: Xử lý form phía Client
Trước Next.js 14 và sự trưởng thành của App Router, việc xử lý form chủ yếu là công việc phía client. Các nhà phát triển phụ thuộc rất nhiều vào trạng thái React (useState) hoặc các thư viện chuyên dụng như React Hook Form hay Formik. Luồng điển hình bao gồm:
- Ngăn chặn sự kiện gửi form mặc định.
- Thu thập và phân tích dữ liệu form.
- Gửi yêu cầu fetch bất đồng bộ đến một API route.
- Quản lý trạng thái
isLoading,isErrorvàisSuccesstheo cách thủ công. - Cập nhật UI dựa trên phản hồi API.
Mặc dù cách tiếp cận này hoạt động, nhưng nó dẫn đến một lượng lớn mã boilerplate và đẩy nhiều JavaScript hơn đến client. Đây là giao diện của một form phía client điển hình:
'use client';
import { useState } from 'react';
export default function TraditionalForm() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
setError('');
const formData = new FormData(e.currentTarget);
const data = Object.fromEntries(formData);
try {
const res = await fetch('/api/submit', {
method: 'POST',
body: JSON.stringify(data),
});
if (!res.ok) throw new Error('Submission failed');
// Handle success
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setLoading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input type="text" name="title" required />
<button type="submit" disabled={loading}>
{loading ? 'Submitting...' : 'Submit'}
</button>
{error && <p>{error}</p>}
</form>
);
}
Mô hình này hiệu quả nhưng dư thừa. Nó yêu cầu một API route hoàn toàn riêng biệt và buộc component phải được đánh dấu bằng 'use client', làm tăng kích thước gói client.
Tiêu chuẩn mới: Next.js 14 Server Actions
Server Actions đảo ngược mô hình này bằng cách cho phép bạn định nghĩa các hàm server bất đồng bộ trực tiếp trong các React component của bạn (hoặc trong các tệp riêng biệt) có thể được gọi trực tiếp từ thuộc tính action của một phần tử <form>.
Server Actions là gì?
Về cốt lõi, Server Actions là các hàm bất đồng bộ được thực thi trên server. Chúng tích hợp liền mạch với hệ thống caching và revalidation của Next.js, nghĩa là bạn có thể thay đổi dữ liệu và cập nhật UI ngay lập tức mà không cần viết logic vô hiệu hóa trạng thái thủ công.
Hãy xem cách chúng ta có thể tái cấu trúc ví dụ trước bằng cách sử dụng Server Actions:
import { revalidatePath } from 'next/cache';
// This function runs exclusively on the server
async function submitData(formData) {
'use server';
const title = formData.get('title');
const content = formData.get('content');
// Perform database mutation here
// await db.posts.create({ title, content });
// Revalidate the cache to show the new post
revalidatePath('/posts');
}
export default function ServerActionForm() {
return (
<form action={submitData}>
<input type="text" name="title" required />
<textarea name="content" required />
<button type="submit">Submit</button>
</form>
);
}
Lưu ý sự vắng mặt của 'use client', useState và fetch. Form này hoạt động ngay cả khi người dùng đã tắt JavaScript trong trình duyệt của họ, cung cấp Progressive Enhancement ngay lập tức. Nhưng còn trạng thái tải và lỗi xác thực thì sao? Đó là lúc các hook mới của React và Zod phát huy tác dụng.
Quản lý trạng thái với useFormState và useFormStatus
Để tạo trải nghiệm người dùng phong phú, chúng ta vẫn cần cung cấp phản hồi trong quá trình gửi form. React đã giới thiệu hai hook mới được thiết kế đặc biệt cho các form sử dụng Server Actions.
useFormStatus
useFormStatus cung cấp thông tin trạng thái của lần gửi form cuối cùng. Nó phải được sử dụng trong một component được render bên trong một <form>.
'use client';
import { useFormStatus } from 'react-dom';
export function SubmitButton() {
const { pending } = useFormStatus();
return (
<button type="submit" disabled={pending} className="bg-blue-600 text-white px-4 py-2 rounded">
{pending ? 'Submitting...' : 'Save Post'}
</button>
);
}
useFormState
useFormState cho phép bạn cập nhật trạng thái dựa trên kết quả của Server Action. Nó hoàn hảo để xử lý lỗi xác thực và thông báo thành công. Hãy kết hợp useFormState với Zod để xác thực mạnh mẽ, dựa trên schema.
Xác thực mạnh mẽ với Zod
Zod là một thư viện khai báo và xác thực schema ưu tiên TypeScript. Nó cực kỳ mạnh mẽ khi được kết hợp với Server Actions để đảm bảo dữ liệu đến cơ sở dữ liệu của bạn chính xác như bạn mong đợi.
Đầu tiên, định nghĩa schema và hình dạng của trạng thái form của bạn:
import { z } from 'zod';
const postSchema = z.object({
title: z.string().min(3, 'Title must be at least 3 characters'),
content: z.string().min(10, 'Content must be at least 10 characters'),
});
Tiếp theo, tạo Server Action. Nó sẽ sử dụng safeParse của Zod để xác thực FormData đến.
'use server';
import { revalidatePath } from 'next/cache';
// Assume postSchema is imported here
export async function createPost(prevState, formData) {
// Validate form fields using Zod
const validatedFields = postSchema.safeParse({
title: formData.get('title'),
content: formData.get('content'),
});
// If form validation fails, return errors early
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
message: 'Missing Fields. Failed to Create Post.',
};
}
// Mutate data
try {
// await db.posts.create(validatedFields.data);
revalidatePath('/posts');
return { message: 'Post created successfully!', errors: {} };
} catch (error) {
return { message: 'Database Error: Failed to Create Post.', errors: {} };
}
}
Cuối cùng, kết nối tất cả lại với nhau trong một Client Component. Trong khi việc render form và quản lý trạng thái diễn ra trên client, thì việc thực thi và xác thực thực tế vẫn được bảo mật trên server.
'use client';
import { useFormState } from 'react-dom';
import { createPost } from '@/app/actions';
import { SubmitButton } from './SubmitButton';
export default function CreatePostForm() {
const initialState = { message: null, errors: {} };
const [state, dispatch] = useFormState(createPost, initialState);
return (
<form action={dispatch} className="flex flex-col gap-4 max-w-md">
<div>
<label htmlFor="title">Title</label>
<input id="title" name="title" type="text" className="border p-2 w-full" />
{state.errors?.title && (
<p className="text-red-500 text-sm mt-1">{state.errors.title[0]}</p>
)}
</div>
<div>
<label htmlFor="content">Content</label>
<textarea id="content" name="content" className="border p-2 w-full" />
{state.errors?.content && (
<p className="text-red-500 text-sm mt-1">{state.errors.content[0]}</p>
)}
</div>
<SubmitButton />
{state.message && (
<p className="text-sm text-gray-700 bg-gray-100 p-3 rounded" aria-live="polite">
{state.message}
</p>
)}
</form>
);
}
Client Components so với Server Actions: Khi nào nên sử dụng cái gì?
Mặc dù Server Actions đại diện cho tương lai của các thay đổi dữ liệu trong Next.js, nhưng Client Components truyền thống vẫn có vị trí của chúng trong các ứng dụng phức tạp.
Sử dụng Server Actions Khi:
- Bạn cần thay đổi dữ liệu một cách an toàn (tương tác cơ sở dữ liệu, gọi API với các bí mật ẩn).
- Bạn muốn cải tiến dần dần (các form hoạt động mà không cần bật JavaScript).
- Bạn muốn giảm kích thước gói phía client.
- Bạn muốn tái xác thực bộ nhớ cache được kết nối chặt chẽ (sử dụng
revalidatePathhoặcrevalidateTag).
Sử dụng xử lý phía Client Khi:
- Bạn yêu cầu các trình hướng dẫn phức tạp, nhiều bước trong đó trạng thái cần duy trì qua các bước mà không cần liên tục truy cập server.
- Bạn cần xác thực theo thời gian thực, từng phím bấm trước khi gửi.
- Bạn phụ thuộc nhiều vào các thư viện phía client của bên thứ ba phức tạp giả định môi trường trình duyệt và yêu cầu tích hợp trạng thái cục bộ.
Kết luận
Next.js 14 đã đơn giản hóa đáng kể trải nghiệm của nhà phát triển xung quanh việc xử lý form. Bằng cách áp dụng Server Actions, useFormState, useFormStatus và Zod, bạn có thể xây dựng các form có khả năng phục hồi cao, hiệu suất tốt và an toàn kiểu dữ liệu. Kiến trúc này không chỉ giảm gánh nặng nhận thức trong việc quản lý các trạng thái tải và lỗi tùy ý theo cách thủ công mà còn thực thi sự tách biệt rõ ràng hơn giữa việc render phía client và thực thi phía server.
Khi hệ sinh thái React tiếp tục phát triển, việc chuẩn hóa các API gốc này sẽ đảm bảo các ứng dụng của bạn vẫn mạnh mẽ, dễ tiếp cận và sẵn sàng cho tương lai của web. Việc chuyển từ các cách tiếp cận nặng nề trong quá khứ sang các phương pháp có khả năng cải tiến dần dần này là một bước quan trọng đối với bất kỳ nhà phát triển web hiện đại nào.
Bạn cũng có thể thích
Free In-Browser Developer Tools
Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.
Related Articles

Quản lý trạng thái trong React 2026: Vượt xa Redux
Hướng dẫn toàn diện về quản lý trạng thái React năm 2026: so sánh React 19 actions, trạng thái máy chủ TanStack Query, Zustand, Jotai và Signals.
Read more
React Thực Chiến: Hiểu Đúng Hooks, State Management và Next.js App Router
Hướng dẫn thực chiến về React Hooks từ cơ bản đến nâng cao — useState, useEffect, useRef, useContext, useMemo, useCallback, Custom Hooks — kết hợp với Zustand, TanStack Query và Next.js App Router. Giải thích tại sao, không chỉ là cách làm.
Read more
suppressHydrationWarning trong Next.js: Hướng dẫn sử dụng an toàn & gỡ lỗi đầy đủ
Hướng dẫn toàn diện về suppressHydrationWarning trong Next.js: sử dụng an toàn & gỡ lỗi đầy đủ với các ví dụ thực tế đã được kiểm chứng.
Read more