React Roadmap: Next.js là gì? SSR và SSG với React

Chào mừng bạn trở lại với series “React Roadmap”! Chúng ta đã cùng nhau đi qua những kiến thức nền tảng vững chắc của React, từ cách React hoạt động, cách sử dụng Functional Components và Hooks (useState, useEffect, useCallback, useMemo, useRef), quản lý Props và State, xử lý sự kiện, làm việc với danh sách, cho đến routing, data fetching và quản lý state phức tạp hơn với Redux Toolkit hay Context API. Tuy nhiên, khi bạn muốn đưa ứng dụng React của mình lên môi trường production một cách hiệu quả nhất, chỉ sử dụng React và create-react-app (CRA) có thể chưa đủ.

Đây là lúc các framework xây dựng trên React phát huy sức mạnh. Trong số đó, Next.js nổi lên như một giải pháp hàng đầu, mang đến những khả năng mạnh mẽ như Server-Side Rendering (SSR) và Static Site Generation (SSG) mà React client-side rendering thuần túy không có được. Bài viết này sẽ giúp bạn hiểu rõ Next.js là gì và tại sao SSR/SSG lại quan trọng.

Next.js là gì? Tại sao lại cần nó?

Next.js là một React framework mã nguồn mở, được phát triển bởi Vercel. Nó cung cấp một cấu trúc và các tính năng cần thiết để xây dựng các ứng dụng web React production-ready một cách hiệu quả.

React, khi được sử dụng với các công cụ như CRA, thường hoạt động theo mô hình Client-Side Rendering (CSR). Điều này có nghĩa là trình duyệt sẽ tải một file HTML tối thiểu, sau đó tải JavaScript, và cuối cùng JavaScript mới chạy để render giao diện người dùng. Mặc dù cách tiếp cận này rất linh hoạt và mang lại trải nghiệm người dùng mượt mà sau khi tải xong, nó có một số nhược điểm:

  • SEO (Search Engine Optimization): Các công cụ tìm kiếm gặp khó khăn khi crawl và index nội dung được render hoàn toàn bằng JavaScript. Mặc dù các bot hiện đại đã tốt hơn, việc cung cấp HTML đầy đủ từ server vẫn là phương pháp đáng tin cậy nhất.
  • Hiệu năng ban đầu (Initial Performance): Người dùng phải đợi JavaScript được tải và chạy trước khi nội dung hiển thị. Điều này có thể dẫn đến trải nghiệm “màn hình trắng” hoặc “loading” kéo dài, ảnh hưởng đến First Contentful Paint (FCP) và Largest Contentful Paint (LCP) – các chỉ số quan trọng về tốc độ tải trang.
  • Chia sẻ trên mạng xã hội: Các nền tảng mạng xã hội khi tạo bản xem trước (preview) thường chỉ đọc thẻ meta trong HTML ban đầu. Với CSR, nội dung động được render sau này sẽ không xuất hiện trong bản xem trước.

Next.js ra đời để giải quyết những vấn đề này bằng cách cung cấp các chiến lược rendering khác nhau, cho phép bạn chọn cách tối ưu nhất cho từng trang của ứng dụng.

Server-Side Rendering (SSR) trong Next.js

Server-Side Rendering (SSR) là một kỹ thuật mà trang web được render thành HTML trên server cho mỗi yêu cầu của người dùng. Khi trình duyệt gửi yêu cầu đến server, server sẽ xử lý logic của trang, lấy dữ liệu cần thiết, render component React thành chuỗi HTML và gửi về cho trình duyệt. Trình duyệt nhận được HTML đầy đủ và có thể hiển thị nội dung ngay lập tức.

Sau khi HTML được hiển thị, JavaScript sẽ được tải về và chạy ở phía client để “hydrate” (phục hồi) ứng dụng. Quá trình hydration gắn các trình xử lý sự kiện và làm cho ứng dụng trở nên tương tác. Lúc này, ứng dụng hoạt động như một ứng dụng React SPA thông thường.

Cách hoạt động của SSR trong Next.js

Trong Next.js, bạn có thể triển khai SSR bằng cách sử dụng hàm bất đồng bộ getServerSideProps trong các trang của mình. Hàm này sẽ chạy trên server mỗi khi có yêu cầu đến trang đó.

Ví dụ:

import React from 'react';

function UserPage({ userData }) {
  // userData được truyền từ getServerSideProps
  return (
    <div>
      <h1>Thông tin người dùng</h1>
      <p>Tên: {userData.name}</p>
      <p>Email: {userData.email}</p>
    </div>
  );
}

export async function getServerSideProps(context) {
  // Hàm này chạy trên server mỗi khi có yêu cầu đến trang /users/:id
  // context chứa thông tin request (ví dụ: query params, headers,...)

  const userId = context.params.id; // Nếu sử dụng dynamic routing [id].js

  // Gọi API hoặc lấy dữ liệu từ database
  const res = await fetch(`https://api.example.com/users/${userId}`);
  const userData = await res.json();

  // Trả về props cho component UserPage
  return {
    props: {
      userData,
    },
  };
}

export default UserPage;

Trong ví dụ trên, khi người dùng truy cập trang, getServerSideProps sẽ được gọi trên server. Nó lấy dữ liệu người dùng từ API dựa trên ID trong URL, sau đó truyền dữ liệu này như props cho component UserPage. Component này render thành HTML và được gửi về trình duyệt.

Ưu điểm của SSR

  1. Tối ưu SEO tốt hơn: Công cụ tìm kiếm nhận được HTML đầy đủ ngay lập tức, giúp crawl và index nội dung hiệu quả hơn.
  2. Tăng tốc độ hiển thị ban đầu (Perceived Performance): Người dùng nhìn thấy nội dung trang nhanh hơn vì HTML đã có sẵn khi tải về, không cần đợi JavaScript chạy.
  3. Phù hợp với nội dung động, thường xuyên thay đổi: Vì trang được render trên server cho mỗi request, dữ liệu hiển thị luôn là mới nhất.
  4. Trải nghiệm chia sẻ trên mạng xã hội tốt hơn: Các thẻ meta và nội dung ban đầu có trong HTML giúp hiển thị bản xem trước chính xác hơn.

Nhược điểm của SSR

  1. Tăng tải cho server: Server phải xử lý việc render HTML cho mỗi yêu cầu, có thể tốn tài nguyên hơn so với chỉ phục vụ các file tĩnh.
  2. Time To First Byte (TTFB) có thể chậm hơn SSG: Server cần thời gian để xử lý logic, lấy dữ liệu và render trang trước khi gửi phản hồi đầu tiên.
  3. Phức tạp hơn trong triển khai: Cần cân nhắc môi trường server và cách quản lý trạng thái ứng dụng trên cả server và client.

Static Site Generation (SSG) trong Next.js

Static Site Generation (SSG) là một kỹ thuật mà trang web được render thành HTML tại thời điểm build ứng dụng. Toàn bộ các trang HTML tĩnh được tạo ra trước và sẵn sàng để phục vụ.

Khi người dùng yêu cầu một trang được tạo tĩnh, server (hoặc CDN – Content Delivery Network) chỉ việc trả về file HTML tĩnh đã được tạo sẵn. Điều này cực kỳ nhanh chóng vì không cần bất kỳ xử lý nào trên server tại thời điểm request.

Tương tự SSR, sau khi HTML được hiển thị, JavaScript sẽ tải về và hydrate ứng dụng để nó trở nên tương tác.

Cách hoạt động của SSG trong Next.js

Trong Next.js, bạn sử dụng hàm bất đồng bộ getStaticProps để triển khai SSG. Hàm này sẽ chạy chỉ một lần tại thời điểm build. Dữ liệu mà nó trả về sẽ được sử dụng để render trang thành HTML tĩnh.

Ví dụ đơn giản:

import React from 'react';

function PostsPage({ posts }) {
  return (
    <div>
      <h1>Bài viết</h1>
      <ul>
        {posts.map(post => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

export async function getStaticProps() {
  // Hàm này chạy tại thời điểm build
  // Lấy dữ liệu các bài viết
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();

  // Trả về props cho component PostsPage
  return {
    props: {
      posts,
    },
    // revalidate: 60 // Tùy chọn: Incremental Static Regeneration (ISR)
  };
}

export default PostsPage;

Đối với các trang động (ví dụ: /posts/[id].js), bạn cần sử dụng thêm hàm getStaticPaths cùng với getStaticProps. getStaticPaths sẽ xác định danh sách các đường dẫn (paths) cần được tạo tĩnh tại thời điểm build.

import React from 'react';

function PostPage({ post }) {
  return (
    <div>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </div>
  );
}

export async function getStaticPaths() {
  // Hàm này chạy tại thời điểm build để xác định các paths
  const res = await fetch('https://api.example.com/posts');
  const posts = await res.json();

  // Lấy danh sách các post ID và tạo params cho paths
  const paths = posts.map(post => ({
    params: { id: post.id.toString() },
  }));

  // 'fallback: false' nghĩa là chỉ những paths được liệt kê ở trên mới được tạo tĩnh
  // Nếu người dùng truy cập một path không có trong danh sách, sẽ trả về 404
  // 'fallback: true' hoặc 'blocking' cho phép tạo trang mới theo yêu cầu (ISR)
  return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
  // Hàm này chạy tại thời điểm build cho MỖI path đã được xác định bởi getStaticPaths
  const res = await fetch(`https://api.example.com/posts/${params.id}`);
  const post = await res.json();

  return {
    props: { post },
    // revalidate: 10 // Tùy chọn: Tái tạo lại trang sau 10 giây
  };
}

export default PostPage;

Ưu điểm của SSG

  1. Hiệu năng cực cao: Trang được phục vụ dưới dạng file HTML tĩnh từ CDN, tốc độ tải gần như tức thì. TTFB rất thấp.
  2. Giảm tải cho server: Server không cần xử lý request động, chỉ đơn giản là phục vụ file tĩnh.
  3. Tối ưu SEO rất tốt: Giống như SSR, công cụ tìm kiếm nhận được HTML đầy đủ.
  4. Độ tin cậy cao: Vì chỉ phục vụ file tĩnh, ít có khả năng gặp lỗi server runtime.

Nhược điểm của SSG

  1. Chỉ phù hợp với nội dung ít thay đổi hoặc không thay đổi: Nếu nội dung thay đổi thường xuyên, bạn cần rebuild và deploy lại ứng dụng để cập nhật trang tĩnh.
  2. Không phù hợp cho các trang cần dữ liệu thời gian thực: Dữ liệu hiển thị trên trang tĩnh là dữ liệu tại thời điểm build. Mọi cập nhật dữ liệu sau đó sẽ không tự động xuất hiện trừ khi sử dụng kỹ thuật client-side fetching bổ sung hoặc ISR.
  3. Thời gian build có thể lâu: Nếu ứng dụng có hàng nghìn trang cần tạo tĩnh, quá trình build có thể mất nhiều thời gian.

So sánh SSR và SSG: Chọn lựa nào cho trang của bạn?

Việc lựa chọn giữa SSR và SSG phụ thuộc vào tính chất của từng trang trong ứng dụng của bạn. Next.js cho phép bạn sử dụng cả hai chiến lược này trong cùng một dự án (per-page basis).

Dưới đây là bảng so sánh giúp bạn hình dung rõ hơn:

Tiêu chí Server-Side Rendering (SSR) Static Site Generation (SSG)
Thời điểm Render Tại thời điểm yêu cầu (request time) Tại thời điểm build (build time)
Tốc độ hiển thị ban đầu Nhanh hơn CSR (HTML có sẵn) Nhanh nhất (HTML tĩnh)
TTFB (Time To First Byte) Có thể chậm hơn SSG (cần xử lý server) Rất nhanh (phục vụ file tĩnh)
SEO Rất tốt (HTML đầy đủ) Rất tốt (HTML đầy đủ)
Phù hợp với nội dung Động, thường xuyên thay đổi (ví dụ: trang dashboard cá nhân, kết quả tìm kiếm) Tĩnh hoặc ít thay đổi (ví dụ: trang blog, trang sản phẩm, trang giới thiệu)
Yêu cầu Server Cần server runtime để render mỗi request Không cần server runtime để render (chỉ cần phục vụ file tĩnh)
Khả năng Scale Tùy thuộc vào năng lực server Rất dễ scale (phục vụ từ CDN)
Thời gian Build Nhanh (không render trang cụ thể) Có thể lâu nếu số lượng trang lớn
Cập nhật nội dung Luôn hiển thị nội dung mới nhất với mỗi request Cần rebuild/deploy hoặc sử dụng ISR để cập nhật

Incremental Static Regeneration (ISR)

Next.js cũng cung cấp một kỹ thuật lai giữa SSG và SSR gọi là Incremental Static Regeneration (ISR). Với ISR, bạn vẫn tạo các trang tĩnh tại thời điểm build (như SSG), nhưng bạn có thể chỉ định một khoảng thời gian (ví dụ: revalidate: 60) để Next.js kiểm tra và tạo lại trang đó ngầm trong nền khi có yêu cầu đến sau khoảng thời gian đó.

Điều này cho phép bạn có được hiệu năng của SSG (phục vụ trang tĩnh) nhưng vẫn có khả năng cập nhật nội dung mà không cần rebuild toàn bộ ứng dụng. ISR rất hữu ích cho các trang có nội dung thay đổi nhưng không cần hiển thị dữ liệu “thời gian thực” trong mili giây.

Next.js còn gì nữa?

Ngoài SSR và SSG, Next.js còn cung cấp nhiều tính năng khác giúp đơn giản hóa việc phát triển ứng dụng React:

  • File-system Routing: Tự động cấu hình route dựa trên cấu trúc file và folder trong thư mục pages.
  • API Routes: Cho phép bạn xây dựng các API endpoint backend ngay trong cùng dự án Next.js. Điều này rất tiện lợi cho các ứng dụng “full-stack” nhỏ hoặc khi bạn cần một lớp backend đơn giản để xử lý dữ liệu trước khi gửi đến client.
  • Image Optimization: Component next/image tự động tối ưu hóa, resize và lazy load hình ảnh để cải thiện hiệu năng.
  • Code Splitting: Tự động chia nhỏ code JavaScript cho từng trang, chỉ tải lượng code cần thiết cho trang hiện tại.
  • CSS Support: Hỗ trợ linh hoạt các phương pháp styling như CSS Modules, Styled Components (so sánh Styled Components vs Emotion), Tailwind CSS (tìm hiểu Tailwind CSS),…
  • Fast Refresh: Trải nghiệm phát triển tuyệt vời với khả năng cập nhật code nhanh chóng mà không làm mất trạng thái ứng dụng.

Bắt đầu với Next.js

Cách dễ nhất để bắt đầu một dự án Next.js mới là sử dụng create-next-app, tương tự như cách bạn dùng create-react-app:

npx create-next-app ten-du-an-nextjs
# hoặc
yarn create next-app ten-du-an-nextjs

Công cụ này sẽ tạo ra một cấu trúc dự án cơ bản với đầy đủ cài đặt để bạn có thể bắt đầu ngay.

Kết luận

Next.js là một bước tiến lớn cho các nhà phát triển React muốn xây dựng ứng dụng web hiệu quả, tối ưu cho cả người dùng và công cụ tìm kiếm. Bằng cách hiểu và tận dụng các chiến lược rendering như Server-Side Rendering (SSR) và Static Site Generation (SSG) (cùng với ISR), bạn có thể cải thiện đáng kể hiệu năng, SEO và trải nghiệm người dùng so với Client-Side Rendering thuần túy.

Việc học Next.js là một phần quan trọng trong Lộ trình học React (React Roadmap) của bạn, mở ra cánh cửa để xây dựng các ứng dụng React phức tạp và chuyên nghiệp hơn. Trong các bài viết tiếp theo, chúng ta sẽ cùng tìm hiểu sâu hơn về các khía cạnh khác của Next.js cũng như các công cụ và kiến thức quan trọng khác trên con đường trở thành một lập trình viên React giỏi.

Hãy tiếp tục theo dõi series và thực hành nhé! Nếu có bất kỳ câu hỏi nào, đừng ngần ngại để lại bình luận.

Chỉ mục