Học viên: Phạm Thanh Huấn
Lớp: Web Frontend - React Js 29
Emai: HuanPT99@gmail.com
Link tham khảo: https://refine.dev/blog/next-js-server-actions-and-data-fetching

Tìm hiểu về Server Actions và Data Fetching trong Next.js 13.4

Tổng quan

Việc mang đến trải nghiệm hoàn hảo cho người dùng và truy xuất dữ liệu có cấu trúc là điều quan trọng nhất trong phát triển web. Next.js, một framework React mạnh mẽ, đã trở thành lựa chọn hàng đầu cho các developers đang tìm cách tối ưu hóa khả năng kết xuất phía máy chủ server-side rendering (SSR) và phía máy khách client-side rendering (CSR). Một khía cạnh quan trọng khiến Next.js trở nên khác biệt là bộ công cụ toàn diện để truy xuất dữ liệu và thao tác trên máy chủ.

Hiểu được nguyên tắc cơ bản của việc truy xuất dữ liệu là điều cần thiết để khai thác sức mạnh của Next.js. Trong bài viết này, chúng ta sẽ đi sâu vào việc truy xuất dữ liệu Next.js và các thao tác phía máy chủ, cách chúng ta có thể sử dụng và quản lý các tính năng của nó trong một ứng dụng. Cho dù bạn là developer Next.js dày dặn kinh nghiệm hay lần đầu tiên khám phá framework này, bài viết này sẽ cung cấp những hiểu biết có giá trị và các ví dụ thực tế để tăng tốc các ứng dụng web của bạn.

Server actions là một tính năng alpha trong Next.js, cho phép bạn xây dựng các điểm cuối máy chủ tùy chỉnh để xử lý các thao tác và hành động cụ thể của dữ liệu. Các đường dẫn API của Next.js cung cấp một nền tảng vững chắc để triển khai server actions một cách hiệu quả, thu hẹp khoảng cách giữa máy khách và máy chủ một cách liền mạch. Chúng ta sẽ đi chi tiết vào sự phức tạp của việc tạo các đường dẫn API, xử lý giao tiếp máy khách-máy chủ và triển khai các hàm phía máy chủ.

Các bước chúng ta đề cập đến gồm :

Khái niệm cơ bản về Next.js

Truy xuất dữ liệu trong Next.js

Server actions trong Next.js

Khái niệm cơ bản về Next.js

Next.js là một framework mạnh mẽ được xây dựng trên React giúp đơn giản hóa và cải thiện việc xây dựng các ứng dụng front-end hiện đại. Nó kết hợp các tính năng tốt nhất của Server-Side Rendering
(SSR) và hiển thị phía máy khách (CSR), cung cấp cho các developers sự linh hoạt và tối ưu hóa hiệu suất.

Server-Side Rendering (SSR) so với Client-Side Rendering (CSR) trong Next.js

Việc render phía máy chủ (Server-Side Rendering - SSR) liên quan đến việc tạo ra mã HTML cho một trang web trên máy chủ và gửi nó đến máy khách. Phương pháp này cải thiện thời gian tải trang ban đầu và cho phép tối ưu hóa công cụ tìm kiếm (SEO) vì trình thu thập thông tin của công cụ tìm kiếm có thể dễ dàng phân tích cú pháp HTML được hiển thị đầy đủ. Next.js vượt trội trong việc hiển thị phía máy chủ, cho phép bạn hiển thị trước các trang một cách động hoặc tĩnh.

Trong khi đó, hiển thị phía máy khách (Client-Side Rendering - CSR) dựa vào JavaScript để hiển thị các trang web trên trình duyệt của máy người dùng. Cách tiếp cận này cung cấp khả năng tương tác và cập nhật nội dung động nhưng có thể dẫn đến việc tải trang ban đầu chậm hơn. Next.js hỗ trợ việc hiển thị hỗn hợp (hybrid rendering), cho phép bạn chọn giữa hiển thị phía máy chủ và phía máy khách theo yêu cầu ứng dụng của bạn.

Ưu điểm của Next.js

  1. Cài đặt đơn giản: Next.js đơn giản hóa quá trình thiết lập bằng cách cung cấp một framework tích hợp đầy đủ chức năng. Nó đi kèm với hệ thống định tuyến tích hợp, cấu hình webpack và hot module replacement, giúp tiết kiệm thời gian và công sức của bạn.

  2. Chia mã tự động: Next.js chia mã của bạn thành các phần nhỏ hơn một cách thông minh, giúp tải trang ban đầu nhanh hơn. Nó chỉ tải các đoạn mã JavaScript cần thiết cho trang hiện tại, giảm băng thông và tối ưu hiệu suất.

  3. Dễ dàng hiển thi phía máy chủ: Next.js giúp cho việc hiển thị phía máy chủ trở nên dễ dàng. Với các hàm như getStaticPropsgetServerSideProps, bạn có thể truy xuất dữ liệu trong quá trình hiển thị phía máy chủ, đảm bảo các trang của bạn có dữ liệu cần thiết trước khi hiển thị. Điều này đặc biệt hữu ích cho các trường hợp tạo tĩnh và hiển thị động.

Truy xuất dữ liệu(Data Fetching) trong Next.js

Truy xuất dữ liệu là một khía cạnh quan trọng của phát triển web và Next.js cung cấp một số phương pháp tích hợp để xử lý việc truy xuất và hiển thị dữ liệu. Các phương pháp này cho phép bạn tìm nạp dữ liệu trong quá trình hiển thị phía máy chủ hoặc ở phía máy khách, tùy thuộc vào yêu cầu cụ thể của bạn. Hãy cùng khám phá các kỹ thuật truy xuất dữ liệu khác nhau được cung cấp bởi Next.js và xem chúng có thể được triển khai như thế nào với các ví dụ code.

Truy xuất dữ liệu phía máy khách (Client-Side Data Fetching)

Next.js cung cấp các hàm cho phép truy xuất dữ liệu ở phía máy khách, cung cấp các bản cập nhật động mà không cần tải lại toàn bộ trang. Hàm được sử dụng phổ biến nhất là useEffect từ React, cho phép chúng tôi thực thi mã sau khi component đã hiển thị. Trong hook useEffect, chúng ta có thể sử dụng fetch API hoặc bất kỳ thư viện nào khác để truy xuất dữ liệu từ điểm cuối API.

import { useEffect, useState } from "react";

function MyComponent() {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      const response = await fetch("https://api.example.com/data");
      const data = await response.json();
      setData(data);
    };

    fetchData();
  }, []);

  return (
    <div>
      {/* Display the fetched data */}
      {data && <p>{data.message}</p>}
    </div>
  );
}

Trong đoạn mã ở trên, chúng ta xác định một function component MyComponent truy xuất dữ liệu từ điểm cuối API bằng cách sử dụng fetch API . Dữ liệu được truy xuất được lưu trữ trong biến trạng thái data bằng cách sử dụng hàm setData. Chúng ta sử dụng hook useEffect với một mảng phụ thuộc trống [] để đảm bảo việc truy xuất dữ liệu chỉ xảy ra một lần khi component được gắn kết.

Truy xuất dữ liệu phía máy chủ (Server-Side Data Fetching)

Next.js cung cấp hai hàm là getStaticPropsgetServerSideProps, để truy xuất dữ liệu phía máy chủ trong quá trình hiển thị. Các hàm này cho phép bạn truy xuất dữ liệu từ API hoặc cơ sở dữ liệu và truyền nó dưới dạng props trong các component trong trang của bạn

export async function getStaticProps() {
  const response = await fetch("https://api.example.com/data");
  const data = await response.json();

  return {
    props: {
      data,
    },
  };
}

function MyPage({ data }) {
  return (
    <div>
      {/* Display the fetched data */}
      {data && <p>{data.message}</p>}
    </div>
  );
}

export default MyPage;

Trong đoạn mã ở trên, chúng ta định nghĩa hàm getStaticProps, đây là một hàm đặc biệt mà Next.js nhận ra để hiển thị trước. Bên trong getStaticProps, chúng ta lấy dữ liệu từ một API và trả về nó dưới dạng prop data. Component MyPage nhận dữ liệu được truy xuất như một prop và hiển thị nó tương ứng.

Điều quan trọng cần lưu ý là getStaticPropsđược sử dụng để tạo tĩnh, trong đó nội dung trang được hiển thị trước tại thời điểm xây dựng, trong khi getServerSideProps được sử dụng để hiển thị phía máy chủ cho mỗi yêu cầu. Bạn có thể chọn hàm thích hợp dựa trên trường hợp sử dụng cụ thể của mình. Next.js cũng cung cấp các tùy chọn truy xuất dữ liệu khác như getInitialProps để hỗ trợ các trường hợp cũ hơn hoặc các kịch bản nâng cao hơn. Các tùy chọn này cung cấp sự linh hoạt trong việc xử lý truy xuất dữ liệu dựa trên nhu cầu ứng dụng của bạn.

Next.js đơn giản hóa quá trình truy xuất dữ liệu bằng cách tích hợp nó một cách mượt mà vào quá trình hiển thị. Cho dù bạn cần truy xuất dữ liệu ở phía máy khách hay trong quá trình hiển thị phía máy chủ, Next.js cung cấp các công cụ cần thiết để giúp việc truy xuất dữ liệu hiệu quả và đơn giản.

Server Actions trong Next.js

Các hành động của máy chủ là một phần không thể thiếu của các ứng dụng web, cho phép bạn xử lý logic phía máy chủ và thực hiện các thao tác như xử lý dữ liệu, xác thực và nhiều hơn nữa. Next.js cung cấp một tính năng mạnh mẽ được gọi là API routes, cho phép bạn tạo các điểm cuối máy chủ tùy chỉnh để xử lý các hành động của máy chủ một cách mượt mà. Hãy khám phá cách triển khai các hành động của máy chủ bằng cách sử dụng API routes trong Next.js với các ví dụ code.

Tạo một API Routes:

Để tạo một API routes trong Next.js, bạn cần tạo một tệp tin bên trong thư mục pages/api. Tệp tin nên được đặt tên dựa trên điểm cuối mong muốn, ví dụ: pages/api/users.js. Trong tệp tin này, bạn có thể định nghĩa logic cho hành động phía máy chủ của mình.

// pages/api/users.js

export default function handler(req, res) {
  // Perform server action
  const myUsers = [
    { id: 1, name: "Odioko" },
    { id: 2, name: "Victor" },
  ];

  // Return the response
  res.status(200).json(myUsers);
}

Trong đoạn mã ở trên, chúng ta tạo một API route users.js bên trong thư mục pages/api. Trong hàm xử lý (handler function), chúng ta thực hiện hành động máy chủ mong muốn, chẳng hạn như lấy danh sách người dùng. Trong trường hợp này, chúng ta xác định một mảng người dùng đơn giản và trả về nó dưới dạng phản hồi bằng cách sử dụng res.status(200).json(users).

Giao tiếp Server Side sử dụng Server Actions

Next.js trình bày một tính năng của server actions, vẫn đang trong giai đoạn alpha. Tính năng này được xây dựng dựa trên React Actions. Với những hành động này, các thay đổi dữ liệu có thể được bắt đầu ở phía máy chủ, dẫn đến giảm thiểu mã JavaScript phía máy khách và cải thiện hiệu suất của các biểu mẫu khi tiến triển theo thời gian.

Nếu bạn muốn thêm server actions trong dự án tiếp theo của mình, hãy vào tệp tin next.config.js của bạn và bật cờ serverActions thử nghiệm.

module.exports = {
  experimental: {
    serverActions: true,
  },
};

Sau khi làm điều này, bạn phải tạo ra Server Action bằng cách định nghĩa một hàm không đồng bộ với chỉ thị”use server" ở đầu phần thân component.

async function myServerAction() {
  "use server";
  // code here
}

Hãy xem một số đoạn code từ docs và giải thích nó đang làm gì.

import { cookies as nextCookies } from "next/headers";

export default function ProductCard({ productId }) {
  async function addToCart(data) {
    "use server";

    const cartId = nextCookies().get("cartId")?.value;
    await saveToDatabase({ cartId, data });
  }

  return (
    <form onSubmit={addToCart}>
      <button type="submit">Add to Cart</button>
    </form>
  );
}

Mã trên là một React functional component trong Next.js gọi là Add To Cart. Nó chịu trách nhiệm hiển thị một biểu mẫu với nút Add To Cart. Hãy chia nhỏ mã và hiểu chức năng của nó.

1 Component được xuất dưới dạng export mặc định, có nghĩa là nó có thể được nhập và sử dụng trong các tệp tin khác.

2 Component lấy một prop là productId, có thể đại diện cho ID của sản phẩm được hiển thị trên thẻ. Prop này có thể được sử dụng để tìm nạp thông tin sản phẩm có liên quan hoặc thực hiện bất kỳ thao tác cần thiết nào.

3 Hàm addToCart là một hàm bất đồng bộ được khai báo bên trong component. Nó được kích hoạt khi biểu mẫu được gửi (tức là khi nhấp vào nút “Add to Cart”). Hàm có quyền truy cập vào tham số data, có thể được truyền vào khi nó được gọi.

4 Bên trong hàm addToCart, có một dòng ”use server". Câu lệnh này ám chỉ rằng đoạn mã sau chỉ nên được thực thi ở phía máy chủ, thay vì chạy ở phía máy khách. Điều này có thể phù hợp nếu bạn đang sử dụng Next.js và muốn phân biệt việc thực thi mã phía máy chủ và phía máy khách.

5 Đoạn mã nextCookies().get('cartId')?.value truy xuất giá trị của cookie cartId bằng cách sử dụng hàm nextCookies từ module next/headers. Toán tử ?. được sử dụng chạy chuỗi tùy chọn, đảm bảo rằng mã không gây ra lỗi nếu cookie hoặc giá trị của nó không tồn tại.

6 Đoạn mã await saveToDatabase({ cartId, data }) đại diện cho tác vụ lưu trữ bất đồng bộ của data cùng với cartId vào cơ sở dữ liệu. Bạn sẽ cần thay thế saveToDatabase bằng hàm thực tế của mình để xử lý logic lưu trữ cơ sở dữ liệu.

7 Component này trả về một phần tử biểu mẫu với trình xử lý sự kiện onSubmit được thiết lập trong hàm addToCart. Điều này có nghĩa là khi biểu mẫu được gửi (bằng cách nhấp vào nút “Add to Cart”), hàm addToCart sẽ được thực thi.

8 Bên trong biểu mẫu, có một nút duy nhất với nội dung Add to Cart kích hoạt việc gửi biểu mẫu khi được nhấp vào.

Cách kích hoạt Server Action

Khi làm việc với Next.js và server actions, có một số phương pháp có sẵn để gọi các hành động này. Hãy tìm hiểu những phương pháp này một cách chi tiết:

Sử dụng prop action: Prop action của React có thể được sử dụng để gọi một hành động phía máy chủ trên một phần tử biểu mẫu như chúng ta có thể thấy ở đoạn mã dưới đây:

export default function TodoApp() {
  async function addTodoItem(data) {
    "use server";

    const todoId = getTodoId().get("todoId")?.value;
    await saveToDb({ todoId, data });
  }

  return (
    <form onSubmit={addTodoItem}>
      <button type="submit">Add Todo</button>
    </form>
  );
}

Trong đoạn mã ở trên, component TodoApp được sử dụng như một ứng dụng todo đơn giản. Nó cho phép người dùng thêm các công việc mới bằng cách gửi biểu mẫu. Hàm addTodoItem, được kích hoạt khi biểu mẫu được gửi, thực hiện các hành động phía máy chủ liên quan đến việc thêm một việc cần làm mới. Nó lấy một ID việc cần làm bằng cách sử dụng hàm getTodoId và việc cần làm vào cơ sở dữ liệu bằng cách sử dụng hàm saveToDb. Mã cung cấp một cấu trúc cơ bản cho một ứng dụng todo, cho phép người dùng thêm todos với xử lý phía máy chủ.

Sử dụng prop formAction: Trong đoạn mã Next.js của chúng ta, chúng ta có thể xử lý các thao tác biểu mẫu trên các phần tử như <button> bằng cách sử dụng prop formAction như chúng ta có thể thấy trong đoạn mã bên dưới:

export default function Form() {
  async function Submit() {
    "use server";
    // ...
  }

  async function submitFile() {
    "use server";
    // ...
  }

  return (
    <form action={Submit}>
      <input type="text" name="name" />
      <input type="image" formAction={submitFile} />
      <button type="submit">Submit</button>
    </form>
  );
}

Trong ví dụ trên, hàm Submit được gọi khi biểu mẫu được gửi, trong khi hàm submitFile được gọi khi nhấp vào ô input. Các hành động của máy chủ được kích hoạt bởi prop formAction.

Khởi tạo tùy chỉnh startTransition: Ngoài hai cách thao tác ở máy chủ mà chúng tôi vừa liệt kê ở trên, có một cách khác để chúng ta có thể thực hiện thao tác phía máy chủ. Phương pháp này liên quan đến việc sử dụng startTransition, được cung cấp bởi hook useTransition của React, và chúng ta có thể sử dụng khi chúng ta không thực hiện các thao tác ở máy chủ trên biểu mẫu.

import { useTransition } from "react";
import { addTodo } from "../actions";

function TodoAppClientComponent({ id }) {
  const [isPending, startTransition] = useTransition();

  return (
    <button onClick={() => startTransition(() => addTodo(id))}>Add Todo</button>
  );
}

Trong đoạn mã ở trên, hàm startTransition bọc quanh việc gọi hành động máy chủ addTodo. Khi nút được nhấp, thao tác phía máy chủ được thực thi. Việc sử dụng startTransition cho phép bạn kiểm soát thời điểm gọi server action.

Giao tiếp phía máy khách với API Routes

Next.js làm cho việc giao tiếp với API routes từ mã phía máy khách trở nên dễ dàng. Bạn có thể sử dụng fetch API hoặc bất kỳ thư viện HTTP nào khác để thực hiện các yêu cầu đến các điểm cuối API của bạn.

import { useEffect, useState } from "react";

function MyComponent() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    const fetchUsers = async () => {
      const response = await fetch("/api/users");
      const data = await response.json();
      setUsers(data);
    };

    fetchUsers();
  }, []);

  return (
    <div>
      {users.map((user) => (
        <p key={user.id}>{user.name}</p>
      ))}
    </div>
  );
}

Trong đoạn mã ở trên, chúng ta đinh nghĩa một function component MyComponent lấy danh sách người dùng từ điểm cuối /api/users bằng cách sử dụng fetch API. Dữ liệu lấy ra được lưu trữ trong biến trạng thái users bằng cách sử dụng hàm setUsers. Chúng ta sử dụng hook useEffectvới một mảng phụ thuộc rỗng [] để đảm bảo việc lấy dữ liệu chỉ xảy ra một lần khi thành phần được gắn kết.

Lưu ý rằng khi thực hiện các yêu cầu đến API routes từ mã phía máy khách, bạn có thể sử dụng các URL tương đối như /api/users vì Next.js tự động định tuyến các yêu cầu đến điểm cuối API thích hợp. API routes của Next.js cung cấp một cơ chế mạnh mẽ để xử lý các thao tác phía máy chủ một cách liền mạch trong ứng dụng của bạn. Cho dù bạn cần lấy dữ liệu, thực hiện biến đổi dữ liệu hay xử lý xác thực, API routes cho phép bạn xác định các điểm cuối máy chủ tùy chỉnh một cách dễ dàng.

Kết luận

Chúng ta đã khám phá những điều cơ bản của Next.js, quá trình thiết lập đơn giản và những lợi ích của nó. Chúng ta đã đi sâu vào việc truy xuất dữ liệu, trình bày các phương pháp cả phía máy khách và phía máy chủ với các ví dụ mã. Ngoài ra, chúng ta đã khám phá khái niệm về server actions sử dụng Next.js API routes, cho phép tạo các điểm cuối máy chủ tùy chỉnh để xử lý logic và hoạt động phía máy chủ.

Next.js tiếp tục phát triển và mang lại sức mạnh cho các developers xây dựng các ứng dụng web mang đến trải nghiệm người dùng tuyệt vời. Bằng cách tận dụng khả năng truy xuất dữ liệu và server actions của Next.js, các developers có thể khai thác tiềm năng thực sự của phát triển web hiện đại và luôn đi đầu trong bối cảnh web thay đổi liên tục. Vì vậy, hãy bắt đầu hành trình của bạn với Next.js, khám phá những khả năng rộng lớn của nó và nâng các dự án phát triển web của bạn lên một tầm cao mới.