Series 5 – Next.js Fundamentals

 

Bài 7 : CSR, SSR, SSG và ISR – Các chiến lược Rendering trong Next.js

1. Mở đầu

Khi xây dựng một website bằng React, chúng ta thường tập trung vào việc tạo component, quản lý state và hiển thị giao diện trên trình duyệt. Tuy nhiên, khi ứng dụng phát triển, một câu hỏi quan trọng xuất hiện: Giao diện nên được tạo ra ở đâu và vào thời điểm nào?

Một trang web có thể được render trực tiếp trong trình duyệt, được tạo trên server theo từng request, được tạo sẵn trước khi người dùng truy cập hoặc được tạo sẵn rồi cập nhật theo chu kỳ.

Những cách tiếp cận này được gọi là các chiến lược rendering. Bốn khái niệm phổ biến là:

  • CSR (Client-Side Rendering)
  • SSR (Server-Side Rendering)
  • SSG (Static Site Generation)
  • ISR (Incremental Static Regeneration)

Hiểu rõ sự khác biệt giữa chúng giúp developer lựa chọn phương án phù hợp với yêu cầu về tốc độ tải trang, SEO, độ mới của dữ liệu và chi phí vận hành.

2. CSR là gì?

CSR là viết tắt của Client-Side Rendering, nghĩa là render phía client, thường là trình duyệt của người dùng.

Với cách tiếp cận này, trình duyệt tải HTML ban đầu cùng JavaScript cần thiết, sau đó JavaScript chạy để tạo hoặc cập nhật giao diện dựa trên dữ liệu và trạng thái của ứng dụng.

2.1. CSR hoạt động như thế nào?

Một quy trình CSR điển hình gồm các bước:

  1. Người dùng truy cập website.
  2. Trình duyệt nhận HTML ban đầu và các tài nguyên cần thiết.
  3. Trình duyệt tải và thực thi JavaScript.
  4. Ứng dụng gọi API nếu cần dữ liệu.
  5. React render giao diện dựa trên dữ liệu nhận được.

Ví dụ, trong một ứng dụng React, chúng ta có thể lấy danh sách sản phẩm bằng useEffect:

"use client";

import { useEffect, useState } from "react";

type Product = {
  id: number;
  title: string;
};

export default function ProductList() {
  const [products, setProducts] = useState<Product[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function loadProducts() {
      try {
        const response = await fetch("/api/products");

        if (!response.ok) {
          throw new Error("Không thể tải sản phẩm");
        }

        const data: Product[] = await response.json();
        setProducts(data);
      } finally {
        setLoading(false);
      }
    }

    loadProducts();
  }, []);

  if (loading) {
    return <p>Đang tải sản phẩm...</p>;
  }

  return (
    <ul>
      {products.map((product) => (
        <li key={product.id}>{product.title}</li>
      ))}
    </ul>
  );
}

Trong ví dụ này, dữ liệu được lấy sau khi component được mount ở phía client.

Lưu ý: đây là ví dụ về cách lấy dữ liệu phía client, không phải cách duy nhất để xây dựng CSR. Một ứng dụng Next.js vẫn có thể pre-render HTML ban đầu rồi tiếp tục cập nhật dữ liệu ở phía client.

2.2. Ưu điểm và hạn chế của CSR

Ưu điểm

  • Phù hợp với giao diện có nhiều tương tác.
  • Dễ cập nhật nội dung theo state mà không cần tải lại toàn bộ trang.
  • Có thể giảm nhu cầu render nội dung động ở server cho mỗi lần tương tác.

Hạn chế

  • Nội dung phụ thuộc vào JavaScript có thể xuất hiện muộn nếu cần tải và thực thi nhiều mã.
  • Nếu dữ liệu chỉ được lấy sau khi component mount, người dùng có thể phải chờ thêm một lượt request trước khi nhìn thấy nội dung.
  • SEO và khả năng hiển thị nội dung phụ thuộc vào cách ứng dụng được render và cách công cụ tìm kiếm xử lý JavaScript.

CSR thường phù hợp với dashboard, công cụ quản trị hoặc những khu vực ứng dụng yêu cầu nhiều tương tác. Tuy nhiên, không nên mặc định rằng mọi ứng dụng có tương tác đều phải render hoàn toàn ở client.

3. SSR là gì?

SSR là viết tắt của Server-Side Rendering, nghĩa là render phía server.

Với SSR, server tạo HTML cho request của người dùng thay vì chờ trình duyệt tự tạo toàn bộ giao diện từ JavaScript.

Trong Next.js, SSR thường được nhắc đến khi nội dung cần được tạo động theo từng request, chẳng hạn dựa vào thông tin người dùng, cookie hoặc dữ liệu mới nhất.

3.1. SSR hoạt động như thế nào?

Một quy trình SSR điển hình:

  1. Người dùng gửi request đến website.
  2. Server lấy dữ liệu cần thiết.
  3. Server render giao diện thành HTML.
  4. Trình duyệt nhận và hiển thị HTML.
  5. JavaScript được tải để hydration những phần cần tương tác.

Hydration là quá trình React kết nối logic phía client với HTML đã được render trước đó, giúp các thành phần tương tác hoạt động.

3.2. Ví dụ SSR trong Next.js

Với App Router, một Server Component có thể đọc dữ liệu động trong quá trình xử lý request.

import { cookies } from "next/headers";

export const dynamic = "force-dynamic";

export default async function AccountPage() {
  const cookieStore = await cookies();
  const session = cookieStore.get("session");

  return (
    <main>
      <h1>Tài khoản</h1>
      <p>
        {session
          ? "Đã nhận được cookie phiên."
          : "Chưa có cookie phiên."}
      </p>
    </main>
  );
}

Ví dụ này minh họa việc đọc cookie phía server. dynamic = "force-dynamic" yêu cầu route render động thay vì dùng Full Route Cache cho kết quả của route.

Trong ứng dụng thực tế, việc có cookie không chứng minh người dùng đã được xác thực. Server cần kiểm tra session hợp lệ và quyền truy cập trước khi hiển thị dữ liệu riêng tư.

3.3. Ưu điểm và hạn chế của SSR

Ưu điểm

  • HTML có thể chứa nội dung phù hợp với request ngay từ đầu.
  • Phù hợp với dữ liệu cần cập nhật theo request.
  • Có thể hỗ trợ SEO vì nội dung được tạo trên server.

Hạn chế

  • Server cần xử lý request và có thể phải lấy dữ liệu trước khi trả về HTML.
  • Việc render động có thể tăng tải server.
  • Nội dung vẫn có thể cần JavaScript để tương tác sau hydration.

SSR phù hợp với trang tài khoản, trang cá nhân hóa hoặc các trang cần tạo nội dung động theo request. Tuy nhiên, dữ liệu riêng tư vẫn cần được bảo vệ bằng xác thực và phân quyền, không chỉ dựa vào việc render trên server.

4. SSG là gì?

SSG là viết tắt của Static Site Generation, nghĩa là tạo trang tĩnh trước khi người dùng truy cập.

Thay vì tạo HTML cho mỗi request, hệ thống tạo HTML trong quá trình build hoặc ở một giai đoạn tạo trang phù hợp. Khi người dùng truy cập, server hoặc CDN có thể trả về nội dung đã được tạo sẵn.

4.1. SSG hoạt động như thế nào?

Quy trình điển hình:

  1. Developer build ứng dụng.
  2. Next.js lấy dữ liệu cần thiết và tạo các trang tĩnh đủ điều kiện.
  3. HTML được triển khai lên server hoặc CDN.
  4. Khi người dùng truy cập, nội dung đã tạo sẵn được trả về.

Vì không cần render lại toàn bộ trang theo từng request, SSG có thể giúp giảm độ trễ và chi phí xử lý.

4.2. Ví dụ tạo trang tĩnh trong Next.js

Giả sử website có trang giới thiệu công ty, nội dung ít thay đổi:

export default function AboutPage() {
  return (
    <main>
      <h1>Về chúng tôi</h1>
      <p>
        Chúng tôi xây dựng các giải pháp phần mềm
        phục vụ doanh nghiệp.
      </p>
    </main>
  );
}

Đây là một trang phù hợp để được prerender thành HTML tĩnh nếu không có yêu cầu khiến route phải render động.

Trong Next.js App Router, các route có thể được prerender tự động nếu đủ điều kiện. Vì vậy, không phải lúc nào bạn cũng cần cấu hình riêng để sử dụng SSG.

4.3. Ưu điểm và hạn chế của SSG

Ưu điểm

  • Có thể trả nội dung rất nhanh nhờ HTML được tạo sẵn.
  • Giảm công việc render lặp lại ở server.
  • Phù hợp với CDN và các trang có nội dung ít thay đổi.
  • Hỗ trợ SEO khi HTML chứa nội dung cần lập chỉ mục.

Hạn chế

  • Nội dung được tạo sẵn có thể lỗi thời nếu nguồn dữ liệu thay đổi sau khi build.
  • Nếu có quá nhiều trang động, việc tạo tất cả trang trước có thể không phù hợp.
  • Dữ liệu cần cá nhân hóa theo từng người dùng không nên được đưa vào HTML tĩnh dùng chung.

SSG thường phù hợp với blog, trang giới thiệu, tài liệu hướng dẫn và các trang nội dung có thể tạo trước.

5. ISR là gì?

ISR là viết tắt của Incremental Static Regeneration, nghĩa là cơ chế tái tạo trang tĩnh theo từng phần hoặc theo từng route mà không nhất thiết phải build lại toàn bộ website mỗi khi dữ liệu thay đổi.

ISR kết hợp lợi ích của trang được tạo sẵn với khả năng cập nhật nội dung sau khi triển khai.

Trong Next.js App Router, ISR thường được thực hiện thông qua các cơ chế cache và revalidation.

5.1. ISR hoạt động như thế nào?

Một quy trình điển hình:

  1. Trang được tạo sẵn và kết quả được cache.
  2. Người dùng truy cập trang, hệ thống trả về kết quả đã cache khi phù hợp.
  3. Khi dữ liệu đến thời điểm revalidate hoặc bị yêu cầu revalidate, cache có thể được làm mới.
  4. Kết quả mới được sử dụng theo chiến lược revalidation đang cấu hình.

Một điểm quan trọng: với cơ chế revalidation theo thời gian và chiến lược stale-while-revalidate, request đầu tiên sau thời hạn có thể vẫn nhận dữ liệu cũ trong lúc dữ liệu mới được tạo. Không nên hiểu ISR là mọi trang tự động thay đổi chính xác ngay tại thời điểm hết hạn.

5.2. Ví dụ ISR trong Next.js

Giả sử trang hiển thị danh sách bài viết, nội dung có thể thay đổi nhưng không cần cập nhật tức thì:

type Post = {
  id: number;
  title: string;
};

async function getPosts(): Promise<Post[]> {
  const response = await fetch(
    "https://example.com/api/posts",
    {
      next: {
        revalidate: 300,
      },
    }
  );

  if (!response.ok) {
    throw new Error("Không thể tải bài viết");
  }

  return response.json();
}

export default async function BlogPage() {
  const posts = await getPosts();

  return (
    <main>
      <h1>Bài viết mới nhất</h1>

      {posts.map((post) => (
        <article key={post.id}>
          <h2>{post.title}</h2>
        </article>
      ))}
    </main>
  );
}

Cấu hình revalidate: 300 yêu cầu Next.js revalidate kết quả fetch theo khoảng thời gian 300 giây.

Để trang được prerender và có thể tái sử dụng HTML đã tạo, route còn phải đáp ứng điều kiện caching và prerender của Next.js. Nếu route bị buộc render động hoặc sử dụng dữ liệu không phù hợp với việc prerender, chỉ cấu hình thời gian cho fetch không có nghĩa toàn bộ trang chắc chắn hoạt động như một trang ISR.

5.3. Khi nào nên dùng ISR?

ISR phù hợp với những trang:

  • Nội dung không thay đổi liên tục từng giây.
  • Cần hiển thị nhanh từ cache.
  • Có thể chấp nhận một khoảng thời gian ngắn trước khi nội dung mới xuất hiện.
  • Có nhiều trang cần cập nhật độc lập mà không muốn build lại toàn bộ website.

Ví dụ: trang sản phẩm, danh sách bài blog, trang danh mục hoặc trang thông tin của một địa điểm.

Đối với dữ liệu giao dịch hoặc thông tin cần độ chính xác tức thời, cần thiết kế chính sách dữ liệu riêng thay vì chỉ dựa vào ISR.

6. So sánh CSR, SSR, SSG và ISR

Tiêu chí CSR SSR SSG ISR
Nội dung được tạo ở đâu? Chủ yếu trong trình duyệt Server theo request Trước khi có request Trang được tạo sẵn và tái tạo khi cần
Thời điểm lấy dữ liệu Thường sau khi JavaScript chạy Trong quá trình xử lý request Khi build hoặc khi prerender Khi tạo trang và trong quá trình revalidation
Độ mới của dữ liệu Theo logic cập nhật phía client Có thể lấy dữ liệu mới theo request Có thể cũ sau build Phụ thuộc chính sách revalidation
Tải xử lý server Ít hơn cho phần render phía client, tùy kiến trúc Có thể cao hơn do render theo request Thấp hơn khi phục vụ trang đã tạo sẵn Giảm render lặp lại nhờ cache
Ví dụ phù hợp Dashboard tương tác Trang cá nhân hóa Trang giới thiệu, tài liệu Blog, trang sản phẩm

Không có chiến lược nào luôn tốt nhất. Một ứng dụng Next.js có thể sử dụng nhiều chiến lược render khác nhau ở những route khác nhau, thậm chí kết hợp nội dung server-rendered với các Client Components bên trong cùng một trang.

7. Cách lựa chọn chiến lược rendering

Khi lựa chọn chiến lược render, hãy bắt đầu bằng ba câu hỏi:

Thứ nhất, dữ liệu thay đổi thường xuyên đến mức nào?

Nếu nội dung rất ổn định, SSG có thể phù hợp. Nếu nội dung thay đổi nhưng có thể chấp nhận cache, ISR là lựa chọn đáng cân nhắc. Nếu cần dữ liệu mới theo từng request, SSR có thể phù hợp hơn.

Thứ hai, trang có phụ thuộc vào người dùng hiện tại không?

Nếu trang hiển thị thông tin riêng tư hoặc cá nhân hóa, bạn cần xem xét cách lấy dữ liệu theo request và bảo vệ quyền truy cập. CSR vẫn có thể được sử dụng cho một số khu vực riêng tư, nhưng việc lấy dữ liệu phải được backend bảo vệ.

Thứ ba, trang cần tương tác đến mức nào?

Nếu trang có nhiều bộ lọc, modal, biểu mẫu và trạng thái giao diện, Client Components có thể xử lý phần tương tác. Điều đó không có nghĩa toàn bộ trang phải sử dụng CSR.

Một ví dụ cho website bán hàng:

  • Trang giới thiệu: thường phù hợp với SSG.
  • Trang danh mục sản phẩm: có thể phù hợp với SSG hoặc ISR.
  • Trang tồn kho cần độ mới cao: cân nhắc lấy dữ liệu động theo yêu cầu nghiệp vụ.
  • Trang tài khoản: cần xác thực và lấy dữ liệu theo quyền truy cập.
  • Bộ lọc sản phẩm tương tác: có thể dùng Client Component để quản lý thao tác phía trình duyệt.

8. Những nhầm lẫn thường gặp

Nghĩ rằng SSR luôn tốt hơn CSR

SSR có thể giúp trả nội dung HTML sớm, nhưng vẫn có chi phí xử lý server và hydration. Hiệu suất phụ thuộc vào dữ liệu, kiến trúc và cách triển khai.

Nghĩ rằng SSG chỉ có thể sử dụng trong quá trình build

SSG truyền thống tạo trang trước khi có request. Tuy nhiên, với Next.js, những trang tĩnh đủ điều kiện còn có thể được tái tạo thông qua cơ chế ISR và revalidation.

Nghĩ rằng ISR luôn hiển thị dữ liệu mới nhất ngay lập tức

Revalidation có thể hoạt động theo cơ chế cache cụ thể. Với chiến lược stale-while-revalidate, dữ liệu cũ vẫn có thể được phục vụ trong lúc làm mới.

Nhầm Server Components với SSR

Server Components là mô hình tổ chức component và môi trường thực thi. SSR là một chiến lược render theo request. Hai khái niệm có liên quan nhưng không đồng nghĩa.

Một Server Component có thể được dùng trong trang prerender tĩnh, trang render động hoặc những mô hình render khác của Next.js.

Nghĩ rằng Client Component đồng nghĩa với CSR hoàn toàn

Client Components vẫn có thể được pre-render thành HTML trong lần tải trang đầu tiên. Sau đó, JavaScript được sử dụng để hydration và thực hiện tương tác phía client.

9. Bài tập thực hành

Hãy thiết kế một website bán hàng đơn giản bằng Next.js App Router với các route sau:

  1. /about: trang giới thiệu ít thay đổi.
  2. /blog: danh sách bài viết.
  3. /products: danh sách sản phẩm.
  4. /account: trang thông tin người dùng.
  5. Một component lọc sản phẩm theo giá.

Với mỗi route, hãy trả lời:

  • Nội dung có cần thay đổi theo từng request không?
  • Có thể chấp nhận dữ liệu cache cũ trong bao lâu?
  • Trang có cần xác thực người dùng không?
  • Phần nào cần tương tác phía client?
  • Chiến lược render nào phù hợp và vì sao?

Sau đó, hãy triển khai một trang nội dung tĩnh, một trang có dữ liệu được revalidate theo thời gian và một Client Component có bộ lọc tương tác.

Mục tiêu không phải là áp dụng đủ cả bốn chiến lược ở mọi nơi, mà là giải thích được lý do lựa chọn cho từng trường hợp.

10. Kết luận

CSR, SSR, SSG và ISR là những chiến lược giúp quyết định nội dung được render ở đâu, vào thời điểm nào và được cập nhật như thế nào.

CSR phù hợp với những phần tương tác phía trình duyệt. SSR phù hợp với nội dung cần được tạo theo request. SSG phù hợp với nội dung có thể tạo sẵn, còn ISR cho phép tái sử dụng trang tĩnh và cập nhật theo chính sách revalidation.

Khi kết hợp những chiến lược này với Server Components, Client Components, Data Fetching và Caching, bạn sẽ có nền tảng tốt hơn để xây dựng ứng dụng Next.js vừa dễ bảo trì vừa đáp ứng yêu cầu về hiệu suất và độ mới của dữ liệu.

Tài liệu tham khảo

Bài viết khác

Series 7 — Web Quality – Engineering :Testing – Unit Testing, Integration Testing, Web Performance – Core Web Vitals, Code Splitting, Lazy Loading

Series 7 — Web Quality – Engineering Bài 3: Testing – Unit Testing, Integration Testing và End-to-End Testing trong ứng dụng web 1. Vì sao ứng dụng web cần Testing? Trong quá trình phát triển Frontend, chúng ta thường kiểm tra ứng dụng bằng cách chạy website trên trình duyệt, nhấn vào các nút, nhập […]

Series 7 — Web Quality – Engineering : Authentication & Authorization

Series 7 — Web Quality – Engineering Bài 1: Authentication & Authorization – Xác thực và phân quyền trong ứng dụng web 1. Mở đầu Khi xây dựng một ứng dụng web có chức năng đăng nhập, chúng ta thường nghĩ rằng chỉ cần tạo biểu mẫu nhập email, mật khẩu và kiểm tra thông […]

Series 6 – Next.js Advanced – Production: Next.js Build & Deployment

Series 6 – Next.js Advanced & Production Bài 5: Next.js Build & Deployment – Từ mã nguồn đến môi trường Production 1. Mở đầu Trong quá trình phát triển một ứng dụng web, việc chạy thành công trên máy tính cá nhân chưa có nghĩa là ứng dụng đã sẵn sàng để đưa đến người […]

Series 6 – Next.js Advanced – Production: Next.js Internationalized Routing Next.js Static Export

Series 6 – Next.js Advanced & Production   Bài 3: Next.js Internationalized Routing – Tổ chức URL cho website đa ngôn ngữ 1. Mở đầu Sau khi xây dựng cơ chế dịch nội dung và định dạng theo locale, ứng dụng cần một cách để người dùng truy cập đúng phiên bản ngôn ngữ. Ví […]

Series 6 – Next.js Advanced & Production: Metadata & SEO, Internationalization (i18n) & Localization (l10n)

Series 6 – Next.js Advanced & Production Bài 1: Metadata & SEO trong Next.js – Tối ưu thông tin và khả năng hiển thị trên công cụ tìm kiếm 1. Mở đầu Khi xây dựng một website, việc hoàn thiện giao diện và chức năng mới chỉ là một phần của quá trình phát triển. […]

Series 5 – Rendering – Data Fetching: Server và Client,  Data Fetching, Caching và Revalidation

Series 5 – Rendering – Data Fetching   Bài 5: Server Components, Client Components và Component Boundaries trong Next.js 1. Mở đầu Khi làm việc với React, chúng ta thường xây dựng component bằng JSX, sử dụng useState để quản lý trạng thái và useEffect để xử lý các tác vụ phụ. Phần lớn logic giao […]

Leave a Reply

Your email address will not be published. Required fields are marked *