Trong nhiều năm, mỗi component React đều phải được tải xuống trình duyệt dưới dạng JavaScript, kể cả những component chỉ hiển thị một đoạn văn bản tĩnh. React Server Components (RSC) thay đổi điều đó bằng cách cho phép một phần cây component chạy hoàn toàn trên server và chỉ gửi kết quả render xuống client. Với các framework như Next.js App Router, đây đã trở thành cách làm mặc định. Vậy RSC thực chất là gì, khác SSR ở điểm nào và khi nào thì nên, hay không nên, dùng nó?

Server Components là gì?

Server Component là component React được render trước, trong một môi trường tách biệt với trình duyệt: có thể là lúc build hoặc trên server ở mỗi request. Mã nguồn của nó không được đưa vào bundle JavaScript phía client. Thay vào đó, React tạo ra một định dạng dữ liệu đặc biệt (thường gọi là RSC Payload) mô tả kết quả render, kèm chỗ trống cho các Client Component và đường dẫn tới file JavaScript của chúng.

Điều này mang lại một số khả năng mới:

  • Component có thể là hàm async và truy vấn dữ liệu trực tiếp, không cần useEffect hay API trung gian.
  • Có thể dùng secret, API key hoặc kết nối database mà không lo lộ ra phía trình duyệt.
  • Thư viện nặng chỉ dùng để render (như bộ chuyển Markdown hay highlight code) không làm phình bundle của client.
// app/posts/page.tsx – Server Component mặc định
import { db } from "@/lib/db";
 
export default async function PostsPage() {
  const posts = await db.post.findMany();
  return (
    <ul>
      {posts.map((p) => <li key={p.id}>{p.title}</li>)}
    </ul>
  );
}

RSC khác gì SSR?

Đây là điểm dễ nhầm nhất. Server-Side Rendering (SSR) biến toàn bộ cây component thành HTML trên server, sau đó trình duyệt vẫn phải tải JavaScript của tất cả component để hydrate, tức gắn sự kiện và làm cho trang tương tác được. Với RSC, chỉ Client Component mới cần hydrate; Server Component không bao giờ có mặt trong bundle client.

Hai kỹ thuật không loại trừ nhau. Trong Next.js, Server Component tạo ra RSC Payload, rồi cả Server lẫn Client Component được dùng để prerender HTML cho lần tải đầu tiên. Ở các lần điều hướng sau, client chủ yếu nhận RSC Payload thay vì HTML đầy đủ.

Client Components và ranh giới "use client"

Khi cần tương tác, bạn đánh dấu file bằng chỉ thị "use client" ở dòng đầu. Chỉ thị này tạo ra một ranh giới: file đó cùng mọi module nó import sẽ thuộc về bundle client.

"use client";
import { useState } from "react";
 
export function LikeButton({ initial }: { initial: number }) {
  const [likes, setLikes] = useState(initial);
  return <button onClick={() => setLikes(likes + 1)}>{likes} lượt thích</button>;
}

Một số quy tắc cần nhớ:

  1. Server Component có thể import và render Client Component.
  2. Client Component không thể import trực tiếp Server Component, nhưng có thể nhận nó qua children hoặc props khác. Khi đó Server Component vẫn được render trên server.
  3. Props truyền từ server sang client phải tuần tự hóa (serialize) được: chuỗi, số, object thuần, mảng, Promise, nhưng không phải hàm thông thường hay instance của class.
  4. Context của React không hoạt động trong Server Component. Provider phải được đặt trong một Client Component.

Khi nào nên dùng Server Components?

Nguyên tắc đơn giản: mặc định để component ở server, chỉ chuyển sang client khi thật sự cần. Server Component phù hợp khi:

  • Component chủ yếu hiển thị dữ liệu: trang danh sách, bài viết, trang sản phẩm, footer, menu điều hướng tĩnh.
  • Cần truy cập dữ liệu gần nguồn hoặc dùng thông tin bí mật.
  • Muốn giảm lượng JavaScript gửi xuống, cải thiện tốc độ tải trên thiết bị yếu hoặc mạng chậm.

Ngược lại, hãy dùng Client Component khi cần:

  • State và event handler như onClick, onChange.
  • Hook vòng đời như useEffect, hoặc custom hook có dùng state.
  • API chỉ có trên trình duyệt: window, localStorage, geolocation.

Mẹo thiết kế ranh giới hợp lý

Sai lầm phổ biến là đặt "use client" ở component cha lớn, kéo cả một nhánh cây xuống client. Hãy đẩy ranh giới xuống càng sâu càng tốt: một layout tĩnh vẫn ở server, chỉ ô tìm kiếm bên trong mới là Client Component. Với module chứa logic nhạy cảm, có thể import gói server-only để build báo lỗi ngay nếu ai đó lỡ dùng nó ở client.

Cũng cần lưu ý rằng RSC không phải "viên đạn bạc". Ứng dụng có giao diện tương tác dày đặc như trình soạn thảo, bảng kéo thả hay dashboard thời gian thực vẫn sẽ có phần lớn code ở client. Lợi ích rõ nhất thuộc về các trang nặng nội dung.

Kết luận

React Server Components giúp ta tách bạch rõ ràng phần "hiển thị dữ liệu" và phần "tương tác", từ đó gửi ít JavaScript hơn và truy vấn dữ liệu gọn gàng hơn. Hãy bắt đầu với tư duy server-first, chỉ thêm "use client" cho những mảnh giao diện thật sự cần tương tác, và để ý kiểu dữ liệu đi qua ranh giới. Làm được vậy, bạn sẽ tận dụng tốt mô hình mới mà không biến dự án thành mê cung khó bảo trì.

Nguồn tham khảo