SonTran's avatar
Son Tran
Nextjs

Tìm hiểu về React Server Component

Tìm hiểu về React Server Component
0 views
9 min read
#Nextjs

Giới thiệu

Trước đây khi fetching data là một quy trình đơn giản mà bạn có thể thực hiện dễ dàng trên server, nhưng với React, sau khi mọi thứ được thực hiện ở client, việc fetching data này trở nên phức tạp hơn nhiều vì sẽ phát sinh thêm lỗi và xử lí effect lúc fetching data. Đây là một trong các lí do Server Component xuất hiện. Viết component ở server là một cách điều mới và đơn giản hoá quá trình fetching data trong khi vẫn giữ nguyên được lợi ích của React.

Server Components là gì?

Điều khác biệt lớn nhất giữa client componentsserver components chính là việc render HTML, Server components chỉ render HTML ở phía server, còn client thì ngược lại. Điều này nghe có vẻ như là một nhược điểm khi render HTML dưới server thì các components sẽ kém linh hoạt hơn, nhưng cũng vì các components này không cần chạy trên client nên việc fetching data cũng trở nên dễ dàng hơn. Hãy cùng so sánh qua ví dụ phía dưới.

function ClientComponent() {
  const [data, setData] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(false);

  useEffect(() => {
    setLoading(true);
    setData(undefined);
    setError(false);

    const controller = new AbortController();

    fetch('/api/data', { signal: controller.signal })
      .then((res) => res.json())
      .then((data) => setData(data))
      .catch((error) => setError(true));

    return () => controller.abort();
  }, []);

  if (loading) return <p>Loading...</p>;
  if (error) return <p>Error</p>;

  return <p>{data}</p>;
}
async function ServerComponent() {
  try {
    const data = await fetch('/api/data').then((res) => res.json());
    return <p>{data}</p>;
  } catch (error) {
    return <p>Error</p>;
  }
}

Ngay cả trong ví dụ rất đơn giản này, việc client fetching data rồi hiển thị, bạn vẫn phải viết khá nhiều code để làm điều đó. Tuy nhiên, với việc fetching data và render ở server, đoạn mã thứ hai đã khiến điều này trở nên đơn giản và dễ dàng hơn nhiều.

Lý do cho việc đoạn mã viết ở server lại đơn giản hơn vì sau khi chạy xong, server sẽ trả ra tệp HTML để phía client hiển thị. Điều này có nghĩa khi user gửi request tới server, user sẽ nhận được tệp HTML sau khi server trả về, đồng thời, mã JavaScript của ứng dụng component cũng được gửi tới client. Khi trình duyệt nhận được mã HTML, nó sẽ thực hiện "hydration", tức là nó sẽ kết hợp các phần HTML đã được render trước đó với mã JavaScript để tạo ra các phần tử React tương ứng. Quá trình này quan trọng để đảm bảo rằng trình duyệt có thể quản lý tương tác với ứng dụng một cách hiệu quả sau khi nó đã được render trước trên server. Thay vì phải tạo lại toàn bộ cây DOM từ đầu, quá trình "hydration" cho phép React chỉ cập nhật và bổ sung các phần tử đã được render trước đó trên server.

Ưu điểm của Server Components

Server components là một công cụ hiểu quả để giúp việc fetching data trở nên dễ dàng hơn, nhưng nó còn nhiều các ưu điểm khác mà nó có thể mang lại

  1. Bảo mật (Security) Chính vì việc server components được viết hoàn toàn phía server nên nó giúp bạn giữ an toàn cho API keys hoặc có thể fetching data trực tiếp từ data điều mà bạn không thể làm được ở phía client.

    async function ServerComponent() {
      const user = await db.users.find({ name: 'John' });
    
      return <p>{user.name}</p>;
    }

    Điều này nghe có vẻ nhỏ bé, nhưng thực ra nó mang lại lợi ích to lớn khi mà bạn không cần phải cung cấp public API riêng cho ứng dụng react. Bạn chỉ đơn giản query database trực tiếp từ server component

  2. Hiệu năng (Performance) Chính xác có rất nhiều cách để server components tăng hiệu năng cho ứng dụng của bạn. Đơn giản vì bạn không cần phải chờ đợi như khi client chạy mã js để render HTML.

  3. Bộ nhớ đệm (Caching) Vì tất cả code của bạn chạy ở server nên bạn có thể cache data giữa các request và giữa các user. Ví dụ, nếu bạn có một danh sách các blog bài viết và bạn biết rằng các bài viết này sẽ không thường xuyên thay đổi, bạn có thể cache lại danh sách các bài viết này cho từng user mà không cần thiết phải fetching lại nó từ database.

    async function ServerComponent() {
      // Cache for 1 minute across all requests
      const articles = await cache(db.articles.findAll, { timeSec: 60 });
    
      return <p>{articles.length}</p>;
    }
  4. Tối ưu size (Bundle Size) Một ưu điểm lớn của việc render phía server là việc bạn không cần phải gửi bất cứ đoạn code js nào tới client. Điều này làm giảm đáng kể kích thước bundle size ứng dụng của bạn và khiến việc tải nó trả nên nhanh chóng và dễ dàng hơn.

  5. Tăng tốc tải (Page Load Speeds) Vì render HTML phía server nên việc các component được gửi tới client và hiển thị ngay khi user yêu cầu, user sẽ không phải đợi quá lâu để xem được nội dung của page. Điều này rất quan trọng với SEO khi à Google luôn ưu tiên page có tốc độ load nhanh.

  6. SEO Speaking of SEO, server components are also much better for SEO since the HTML for those components is sent to the client immediatley without having to wait for JS to load. This helps search engines index your pages and makes them easier to find on Google. Nói đến SEO, server components luôn là phương án tối ưu vì các tệp HTML được gửi tới client nhanh chóng. Điều này giúp các công cụ tìm kiếm của Google lập chỉ mục các trang của bạn và giúp tìm thấy chúng dễ dàng hơn trên Google.

Nhược điểm của Server Components

Cũng giống như mọi thứ trên đời, cái gì cũng có hai mặt. Server components cũng không là một ngoại lệ, nó cũng có một số nhược điểm khi bạn sử dụng.

  1. Khả năng tương tác DOM hạn chế (No Interactivity) Vì các components được render phía server nên việc tương tác với DOM là bất khả thi. Điều này có nghĩa là bạn không thể sử dụng React hooks, even listeners, hay bất cứ đoạn code nào liên quan đến sự tương tác với người dùng.

    Điều này nghe có vẻ khó hiểu, nhưng bạn có thể dễ dàng sử dụng client components ngay trong server components mà không gặp bất cứ vấn đề gì.

    async function ServerComponent() {
      const articles = await db.articles.findAll();
    
      return articles.map((article) => <Article article={article} />);
    }
    'use client';
    // This is a normal client component so you can use hooks and event listeners
    function Article({article}) {
      const [likes, setLikes] = useState(0);
    
      return (
        <div>
          <p>{article.title}</p>
          <p>{article.content}</p>
          <button onClick={() => setLikes((l) => l + 1)}>Like</button>
          <p>{likes}</p>
        </div>
      );
    }

    Có thể bạn đã chú ý tới "use client" ở phía bên trên cùng của file. Đây là một tính năng mới đặc biệt trong Next.js, nó thường được sử dụng để báo với Next.js rằng file này là client component. Một số frameworks khác có thể có cách khác để làm việc này, nhưng về cơ bản thì giống nhau.

    Như bạn cũng có thể thấy, bạn vẫn có thể sử dụng server components cho việc fetching data sau đó sử dụng client components để thêm các sự kiện tương tác. Đây là một pattern mạnh mẽ và khiến việc tạo các ứng dụng phức tạp trở nên đơn giản hơn.

  2. Không hỗ trợ Browser API (No Browser APIs) Một nhược điểm nữa đó chính là việc bạn không thể sử dụng các browser API với server components vì nó được render phía server nên không thể truy cập client APIs. Điều này có nghĩa các hành động liên quan tới localStorage, navigator, và window đều không khả dụng. Nhưng nó cũng không là vấn đề khi bạn có thể thay thế bằng client components với "use client".

  3. Lồng trong Client Components (Cannot Easily Be Nested In Client Components) Server components không thể lồng được trong client components. Điều này là hiển nhiên vì client components được render ở phía client nên không thể có server components nằm trong nó.

    function ServerComponent() {
      return <p>Hello Server</p>;
    }
    'use client';
    function ClientComponent() {
      return (
        <div>
          <p>Hello Client</p>
          {/* This will not work */}
          <ServerComponent />
        </div>
      );
    }

    Nhưng bạn có thể có một cách "work around" để giải quyết vấn đề này (ít nhất là với Next.js) bằng cách truyền server components như một prop cho client components và render nó bên trong client components

    function ServerComponent() {
      return <p>Hello Server</p>;
    }
    'use client';
    
    function ClientComponent({ children }) {
      return (
        <div>
          <p>Hello Client</p>
          {children}
        </div>
      );
    }
    function AnotherServerComponent() {
      return (
        <ClientComponent>
          {/* This will work */}
          <ServerComponent />
        </ClientComponent>
      );
    }

    Trong hầu hết các trường hợp, đây thường không phải là điều bạn cần làm, nhưng nếu bạn rơi vào tình huống này thì đây là một giải pháp tốt.

Cách để sử dụng Server components

Hiện tại framework Next.js đã hỗ trợ server components, tất cả các component khi được tạo mặc định sẽ là server components. Để sử dụng client components bạn cần phải thêm "use client" vào trên cùng của file.

'use client';

// This is a client component
function ClientComponent() {
  return <p>Hello Client</p>;
}

Lý do cho việc server components được đặt làm mặc định vì trong hầu hết các trường hợp, server components mang lại hiệu quả tốt hơn trừ khi bạn cần tương tác với browser APIs.

Kết luận

Server components là một cách mới để bạn xây dựng ứng dụng React, nó giúp bạn dễ dàng tạo các ứng dụng phức tạp và giải quyết rất nhiều các vẫn đề liên quan đến data fetching và hiệu năng. Next.js là một framwork sử dụng server components và cung cấp các các tài liệu trực quan, dễ dàng tiếp cận và đọc hiểu về nó.