Tiếng Việt

Hướng dẫn toàn diện về Server Actions trong Next.js 14, bao gồm các phương pháp xử lý form tốt nhất, xác thực dữ liệu, cân nhắc bảo mật và các kỹ thuật nâng cao để xây dựng ứng dụng web hiện đại.

Server Actions trong Next.js 14: Làm chủ các phương pháp xử lý Form tốt nhất

Next.js 14 giới thiệu các tính năng mạnh mẽ để xây dựng ứng dụng web hiệu năng và thân thiện với người dùng. Trong số đó, Server Actions nổi bật như một cách thức đột phá để xử lý việc gửi form và các biến đổi dữ liệu (data mutations) trực tiếp trên máy chủ. Hướng dẫn này cung cấp một cái nhìn tổng quan toàn diện về Server Actions trong Next.js 14, tập trung vào các phương pháp tốt nhất để xử lý form, xác thực dữ liệu, bảo mật và các kỹ thuật nâng cao. Chúng ta sẽ khám phá các ví dụ thực tế và cung cấp những hiểu biết hữu ích để giúp bạn xây dựng các ứng dụng web mạnh mẽ và có khả năng mở rộng.

Next.js Server Actions là gì?

Server Actions là các hàm bất đồng bộ chạy trên máy chủ và có thể được gọi trực tiếp từ các component React. Chúng loại bỏ sự cần thiết của các API route truyền thống để xử lý việc gửi form và biến đổi dữ liệu, giúp mã nguồn đơn giản hơn, cải thiện bảo mật và tăng cường hiệu suất. Server Actions là các React Server Components (RSC), có nghĩa là chúng được thực thi trên máy chủ, giúp trang tải ban đầu nhanh hơn và cải thiện SEO.

Lợi ích chính của Server Actions:

Thiết lập dự án Next.js 14 của bạn

Trước khi đi sâu vào Server Actions, hãy đảm bảo bạn đã thiết lập một dự án Next.js 14. Nếu bạn bắt đầu từ đầu, hãy tạo một dự án mới bằng lệnh sau:

npx create-next-app@latest my-next-app

Hãy chắc chắn rằng dự án của bạn đang sử dụng cấu trúc thư mục app để tận dụng tối đa Server Components và Actions.

Xử lý Form cơ bản với Server Actions

Hãy bắt đầu với một ví dụ đơn giản: một form gửi dữ liệu để tạo một mục mới trong cơ sở dữ liệu. Chúng ta sẽ sử dụng một form đơn giản với một trường nhập liệu và một nút gửi.

Ví dụ: Tạo một mục mới

Đầu tiên, hãy định nghĩa một hàm Server Action bên trong component React của bạn. Hàm này sẽ xử lý logic gửi form trên máy chủ.

// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';

async function createItem(formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  // Mô phỏng tương tác với cơ sở dữ liệu
  console.log('Tạo mục:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Mô phỏng độ trễ

  console.log('Mục đã được tạo thành công!');
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    await createItem(formData);
    setIsSubmitting(false);
  }

  return (
    
); }

Giải thích:

Xác thực dữ liệu

Xác thực dữ liệu là rất quan trọng để đảm bảo tính toàn vẹn của dữ liệu và ngăn chặn các lỗ hổng bảo mật. Server Actions cung cấp một cơ hội tuyệt vời để thực hiện xác thực phía máy chủ. Cách tiếp cận này giúp giảm thiểu rủi ro liên quan đến việc chỉ xác thực phía máy khách.

Ví dụ: Xác thực dữ liệu đầu vào

Sửa đổi Server Action createItem để bao gồm logic xác thực.

// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';

async function createItem(formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  if (!name || name.length < 3) {
    throw new Error('Tên mục phải có ít nhất 3 ký tự.');
  }

  // Mô phỏng tương tác với cơ sở dữ liệu
  console.log('Tạo mục:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Mô phỏng độ trễ

  console.log('Mục đã được tạo thành công!');
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);
    try {
      await createItem(formData);
    } catch (error: any) {
      setErrorMessage(error.message || 'Đã xảy ra lỗi.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Giải thích:

Sử dụng các thư viện xác thực

Đối với các kịch bản xác thực phức tạp hơn, hãy xem xét sử dụng các thư viện xác thực như:

Đây là một ví dụ sử dụng Zod:

// app/utils/validation.ts
import { z } from 'zod';

export const CreateItemSchema = z.object({
  name: z.string().min(3, 'Tên mục phải có ít nhất 3 ký tự.'),
});
// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';
import { CreateItemSchema } from '../utils/validation';

async function createItem(formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  const validatedFields = CreateItemSchema.safeParse({ name });

  if (!validatedFields.success) {
    return { errors: validatedFields.error.flatten().fieldErrors };
  }

  // Mô phỏng tương tác với cơ sở dữ liệu
  console.log('Tạo mục:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Mô phỏng độ trễ

  console.log('Mục đã được tạo thành công!');
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);
    try {
      await createItem(formData);
    } catch (error: any) {
      setErrorMessage(error.message || 'Đã xảy ra lỗi.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Giải thích:

Các cân nhắc về bảo mật

Server Actions tăng cường bảo mật bằng cách thực thi mã trên máy chủ, nhưng vẫn rất quan trọng để tuân theo các phương pháp bảo mật tốt nhất để bảo vệ ứng dụng của bạn khỏi các mối đe dọa phổ biến.

Ngăn chặn Tấn công Giả mạo Yêu cầu Liên trang (CSRF)

Các cuộc tấn công CSRF khai thác sự tin tưởng mà một trang web dành cho trình duyệt của người dùng. Để ngăn chặn các cuộc tấn công CSRF, hãy triển khai các cơ chế bảo vệ CSRF.

Next.js tự động xử lý việc bảo vệ CSRF khi sử dụng Server Actions. Framework tạo và xác thực một mã thông báo CSRF cho mỗi lần gửi form, đảm bảo rằng yêu cầu bắt nguồn từ ứng dụng của bạn.

Xử lý Xác thực và Phân quyền Người dùng

Đảm bảo rằng chỉ những người dùng được ủy quyền mới có thể thực hiện một số hành động nhất định. Triển khai các cơ chế xác thực và phân quyền để bảo vệ dữ liệu và chức năng nhạy cảm.

Đây là một ví dụ sử dụng NextAuth.js để bảo vệ một Server Action:

// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';
import { getServerSession } from 'next-auth';
import { authOptions } from '../../app/api/auth/[...nextauth]/route';

async function createItem(formData: FormData) {
  'use server'

  const session = await getServerSession(authOptions);

  if (!session) {
    throw new Error('Không được phép');
  }

  const name = formData.get('name') as string;

  // Mô phỏng tương tác với cơ sở dữ liệu
  console.log('Tạo mục:', name, 'bởi người dùng:', session.user?.email);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Mô phỏng độ trễ

  console.log('Mục đã được tạo thành công!');
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);
    try {
      await createItem(formData);
    } catch (error: any) {
      setErrorMessage(error.message || 'Đã xảy ra lỗi.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Giải thích:

Làm sạch dữ liệu đầu vào

Làm sạch dữ liệu đầu vào để ngăn chặn các cuộc tấn công Cross-Site Scripting (XSS). Các cuộc tấn công XSS xảy ra khi mã độc được tiêm vào một trang web, có khả năng xâm phạm dữ liệu người dùng hoặc chức năng ứng dụng.

Sử dụng các thư viện như DOMPurify hoặc sanitize-html để làm sạch dữ liệu đầu vào do người dùng cung cấp trước khi xử lý nó trong Server Actions của bạn.

Các kỹ thuật nâng cao

Bây giờ chúng ta đã nắm được những điều cơ bản, hãy khám phá một số kỹ thuật nâng cao để sử dụng Server Actions hiệu quả.

Cập nhật lạc quan (Optimistic Updates)

Cập nhật lạc quan cung cấp trải nghiệm người dùng tốt hơn bằng cách cập nhật giao diện người dùng ngay lập tức như thể hành động sẽ thành công, ngay cả trước khi máy chủ xác nhận. Nếu hành động thất bại trên máy chủ, giao diện người dùng sẽ được hoàn nguyên về trạng thái trước đó.

// app/components/UpdateItemForm.tsx
'use client';

import { useState } from 'react';

async function updateItem(id: string, formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  // Mô phỏng tương tác với cơ sở dữ liệu
  console.log('Cập nhật mục:', id, 'với tên:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Mô phỏng độ trễ

  // Mô phỏng thất bại (cho mục đích minh họa)
  const shouldFail = Math.random() < 0.5;
  if (shouldFail) {
    throw new Error('Cập nhật mục thất bại.');
  }

  console.log('Mục đã được cập nhật thành công!');
  return { name }; // Trả về tên đã cập nhật
}

export default function UpdateItemForm({ id, initialName }: { id: string; initialName: string }) {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  const [itemName, setItemName] = useState(initialName);

  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);

    // Cập nhật giao diện người dùng một cách lạc quan
    const newName = formData.get('name') as string;
    setItemName(newName);

    try {
      const result = await updateItem(id, formData);
      //Nếu thành công thì cập nhật đã được phản ánh trong UI thông qua setItemName

    } catch (error: any) {
      setErrorMessage(error.message || 'Đã xảy ra lỗi.');
      // Hoàn nguyên giao diện người dùng khi có lỗi
      setItemName(initialName);
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    

Tên hiện tại: {itemName}

{errorMessage &&

{errorMessage}

}
); }

Giải thích:

Xác thực lại dữ liệu (Revalidating Data)

Sau khi một Server Action sửa đổi dữ liệu, bạn có thể cần xác thực lại dữ liệu đã được lưu vào bộ nhớ đệm để đảm bảo rằng giao diện người dùng phản ánh những thay đổi mới nhất. Next.js cung cấp một số cách để xác thực lại dữ liệu:

Đây là một ví dụ về việc xác thực lại một đường dẫn sau khi tạo một mục mới:

// app/components/CreateItemForm.tsx
'use client';

import { useState } from 'react';
import { revalidatePath } from 'next/cache';

async function createItem(formData: FormData) {
  'use server'

  const name = formData.get('name') as string;

  // Mô phỏng tương tác với cơ sở dữ liệu
  console.log('Tạo mục:', name);

  await new Promise((resolve) => setTimeout(resolve, 1000)); // Mô phỏng độ trễ

  console.log('Mục đã được tạo thành công!');

  revalidatePath('/items'); // Xác thực lại đường dẫn /items
}

export default function CreateItemForm() {
  const [isSubmitting, setIsSubmitting] = useState(false);
  const [errorMessage, setErrorMessage] = useState(null);
  
  async function handleSubmit(formData: FormData) {
    setIsSubmitting(true);
    setErrorMessage(null);
    try {
      await createItem(formData);
    } catch (error: any) {
      setErrorMessage(error.message || 'Đã xảy ra lỗi.');
    } finally {
      setIsSubmitting(false);
    }
  }

  return (
    
{errorMessage &&

{errorMessage}

}
); }

Giải thích:

Các phương pháp tốt nhất cho Server Actions

Để tối đa hóa lợi ích của Server Actions, hãy xem xét các phương pháp tốt nhất sau:

Những cạm bẫy thường gặp và cách tránh

Mặc dù Server Actions mang lại nhiều lợi ích, có một số cạm bẫy phổ biến cần lưu ý:

Kết luận

Server Actions trong Next.js 14 cung cấp một cách mạnh mẽ và hiệu quả để xử lý việc gửi form và các biến đổi dữ liệu trực tiếp trên máy chủ. Bằng cách tuân theo các phương pháp tốt nhất được nêu trong hướng dẫn này, bạn có thể xây dựng các ứng dụng web mạnh mẽ, an toàn và hiệu năng. Hãy tận dụng Server Actions để đơn giản hóa mã nguồn, tăng cường bảo mật và cải thiện trải nghiệm người dùng tổng thể. Khi bạn tích hợp những nguyên tắc này, hãy xem xét tác động toàn cầu của các lựa chọn phát triển của bạn. Đảm bảo rằng các quy trình xử lý form và dữ liệu của bạn có thể truy cập, an toàn và thân thiện với người dùng cho các đối tượng quốc tế đa dạng. Sự cam kết này về tính toàn diện không chỉ cải thiện khả năng sử dụng của ứng dụng mà còn mở rộng phạm vi và hiệu quả của nó trên quy mô toàn cầu.