Bài 29.2: Incremental Static Regeneration

Trong hành trình xây dựng các ứng dụng web hiện đại với Next.js, chúng ta luôn tìm kiếm sự cân bằng giữa hiệu suấttính tươi mới của dữ liệu. Static Site Generation (SSG) mang lại tốc độ đáng kinh ngạc vì trang đã được render sẵn ở thời điểm build, nhưng dữ liệu có thể nhanh chóng trở nên lỗi thời. Server-Side Rendering (SSR) đảm bảo dữ liệu luôn mới nhất trên mỗi request, nhưng có thể chậm hơn SSG ở lần tải trang đầu tiên do phải chờ dữ liệu và render trên server.

Vậy, làm thế nào để có được cả hai ưu điểm cùng lúc? Đó chính là lúc Incremental Static Regeneration (ISR) tỏa sáng!

Incremental Static Regeneration (ISR) là gì?

Incremental Static Regeneration (ISR) là một tính năng độc đáo của Next.js cho phép bạn tạo hoặc cập nhật các trang static sau khi ứng dụng đã được build và triển khai. Thay vì phải rebuild toàn bộ website mỗi khi có dữ liệu mới (như SSG truyền thống), ISR cho phép tái tạo (regenerate) từng trang riêng lẻ trong nền.

Hãy nghĩ đơn giản:

  1. Ứng dụng của bạn được build và triển khai. Các trang static ban đầu được tạo ra (giống SSG).
  2. Khi một người dùng truy cập một trang đã kích hoạt ISR, họ nhận được phiên bản static đã được cache (cực nhanh!).
  3. Trong nền, Next.js kiểm tra xem đã đến lúc cần tái tạo lại trang này chưa (dựa trên một khoảng thời gian bạn thiết lập).
  4. Nếu đã đến lúc, Next.js sẽ tái tạo trang trong nền bằng cách gọi lại hàm lấy dữ liệu (ví dụ: getStaticProps).
  5. Phiên bản trang mới sẽ được lưu vào cache.
  6. Các người dùng tiếp theo truy cập trang này sẽ nhận được phiên bản mới đã được tái tạo.

Điều này thường được gọi là mô hình "stale-while-revalidate" (tạm dịch: lỗi thời trong khi đang tái tạo). Trang cũ (stale) được phục vụ ngay lập tức trong khi trang mới đang được tạo ra (revalidate) trong nền.

Tại sao ISR lại quan trọng?

ISR giải quyết một vấn đề lớn của SSG truyền thống: khó khăn trong việc cập nhật nội dung thường xuyên. Với SSG, mỗi lần bạn thay đổi dù chỉ một bài blog hay một sản phẩm, bạn có thể phải trigger một quá trình build lại toàn bộ website, tốn kém thời gian và tài nguyên, đặc biệt với các trang web lớn.

  • Hiệu suất SSG: Người dùng luôn nhận được trang static được phục vụ từ CDN, tốc độ tải cực nhanh.
  • Tính tươi mới dữ liệu: Các trang được cập nhật định kỳ mà không cần rebuild toàn bộ website. Dữ liệu gần với thời gian thực hơn SSR nhưng vẫn giữ được ưu điểm về tốc độ của static.
  • Khả năng mở rộng: Giảm tải cho server backend so với SSR truyền thống, vì các request phổ biến được phục vụ bởi static cache.
  • Triển khai đơn giản: Chỉ cần thiết lập một tham số nhỏ trong getStaticProps.
Cách triển khai ISR trong Next.js

ISR được triển khai thông qua tham số revalidate trong hàm getStaticProps.

1. Với các trang static thông thường

Đối với các trang static không có tham số động (ví dụ: trang chủ, trang giới thiệu), bạn chỉ cần thêm revalidate vào getStaticProps.

// pages/index.js
import Head from 'next/head';

function HomePage({ data, lastUpdated }) {
  return (
    <div>
      <Head><title>Trang chủ cập nhật</title></Head>
      <h1>Chào mừng đến với website!</h1>
      <p>Dữ liệu mới nhất: **{data}**</p>
      <p>Cập nhật lần cuối lúc: *{new Date(lastUpdated).toLocaleString()}*</p>
    </div>
  );
}

export async function getStaticProps() {
  // Giả định gọi một API để lấy dữ liệu thường xuyên thay đổi
  const res = await fetch('https://api.your-website.com/latest-info'); // Thay thế bằng API thật
  const data = await res.json();

  return {
    props: {
      data: data.message || 'Dữ liệu mặc định',
      lastUpdated: Date.now(), // Lưu thời điểm fetch dữ liệu
    },
    // Trang này sẽ được tái tạo trong nền sau mỗi 10 giây
    // nếu có request tới trang này.
    revalidate: 10, // Thời gian tính bằng giây (ở đây là 10 giây)
  };
}

export default HomePage;

Giải thích:

  • getStaticProps vẫn làm nhiệm vụ fetch dữ liệu tại thời điểm build lần đầu.
  • Tham số revalidate: 10 nói với Next.js rằng trang này có thể được tái tạo trong nền sau mỗi 10 giây.
  • Khi người dùng đầu tiên truy cập trang sau 10 giây kể từ lần tái tạo trước: Họ nhận phiên bản (static cached). Trong nền, Next.js bắt đầu quá trình fetch dữ liệu mới và render lại trang.
  • Khi người dùng tiếp theo (hoặc chính người dùng đầu tiên refresh sau khi quá trình nền hoàn tất) truy cập: Họ nhận phiên bản mới đã được render và lưu vào cache.
2. Với các trang có đường dẫn động (Dynamic Routes)

ISR đặc biệt mạnh mẽ khi kết hợp với các trang có đường dẫn động, ví dụ như trang chi tiết sản phẩm (/products/[id].js) hay trang bài viết blog (/posts/[slug].js). Bạn cần sử dụng getStaticPathsgetStaticProps cùng với revalidate.

// pages/products/[id].js
import { useRouter } from 'next/router';
import Head from 'next/head';

function ProductPage({ product, lastUpdated }) {
  const router = useRouter();

  // Nếu fallback là true và trang chưa được tạo ra, hiển thị trạng thái loading
  if (router.isFallback) {
    return <div>Đang tải thông tin sản phẩm...</div>;
  }

  // Nếu không tìm thấy sản phẩm sau khi fallback hoặc tái tạo
  if (!product) {
     return <div>Không tìm thấy sản phẩm này.</div>; //  thể thay bằng trang 404 tùy ý
  }

  return (
    <div>
      <Head><title>{product.name}</title></Head>
      <h1>Chi tiết sản phẩm: {product.name}</h1>
      <p>**Giá:** {product.price}</p>
      <p> tả: *{product.description}*</p>
      <p>Cập nhật lần cuối lúc: *{new Date(lastUpdated).toLocaleString()}*</p>
    </div>
  );
}

// getStaticPaths xác định các đường dẫn (sản phẩm) nào sẽ được pre-render tại thời điểm build
export async function getStaticPaths() {
  // Giả định lấy danh sách các sản phẩm phổ biến hoặc nổi bật để pre-render ban đầu
  const res = await fetch('https://api.your-website.com/popular-products'); // Thay thế
  const products = await res.json();

  // Tạo mảng các params cho các đường dẫn cần pre-render
  const paths = products.map((product) => ({
    params: { id: product.id.toString() }, // ID phải là string
  }));

  return {
    paths,
    // fallback: true: Nếu một ID sản phẩm không có trong danh sách `paths` ở trên,
    // Next.js sẽ không trả về 404 ngay lập tức. Thay vào đó, nó sẽ hiển thị
    // trạng thái fallback (router.isFallback = true), fetch dữ liệu cho ID đó
    // và render trang. Trang này sau đó sẽ được thêm vào cache static và áp dụng ISR.
    // fallback: 'blocking': Tương tự fallback: true nhưng trình duyệt sẽ đợi
    // trang được render xong trên server trước khi hiển thị, không có trạng thái loading.
    // fallback: false: Chỉ các đường dẫn trong `paths` tồn tại. Các ID khác sẽ trả về 404.
    fallback: true,
  };
}

// getStaticProps lấy dữ liệu chi tiết cho từng sản phẩm
export async function getStaticProps({ params }) {
  const id = params.id;

  // Lấy dữ liệu chi tiết sản phẩm dựa trên ID
  const res = await fetch(`https://api.your-website.com/products/${id}`); // Thay thế
  const product = await res.json();

  // Xử lý trường hợp API trả về lỗi hoặc không tìm thấy sản phẩm
  if (!product || !product.id) {
    // Trả về notFound: true sẽ hiển thị trang 404
    return {
      notFound: true,
      // Có thể thêm revalidate ở đây để thử lại sau một thời gian xem sản phẩm có được thêm vào không
      revalidate: 60, // Thử lại sau 60 giây
    };
  }

  return {
    props: {
      product,
      lastUpdated: Date.now(),
    },
    // Tái tạo trang sản phẩm này sau mỗi 60 giây nếu có request
    revalidate: 60, // Thời gian tính bằng giây
  };
}

export default ProductPage;

Giải thích:

  • getStaticPaths được sử dụng để thông báo cho Next.js biết những ID sản phẩm nào nên được pre-render ngay lúc build. Thường bạn sẽ chỉ pre-render những sản phẩm phổ biến nhất để tối ưu thời gian build.
  • fallback: true là điểm mấu chốt cho ISR với Dynamic Routes. Nó cho phép:
    • Nếu người dùng truy cập một ID sản phẩm chưa được liệt kê trong getStaticPaths tại thời điểm build, Next.js sẽ không trả về 404 ngay lập tức.
    • Nó hiển thị trạng thái loading (router.isFallback là true).
    • Trong nền, Next.js chạy getStaticProps cho ID sản phẩm đó để lấy dữ liệu và render trang.
    • Sau khi render xong, trang này được phục vụ cho người dùng hiện tại và được lưu vào cache static.
    • Các request sau tới cùng ID sản phẩm này sẽ nhận được trang static đã được cache.
  • Tham số revalidate: 60 trong getStaticProps đảm bảo rằng mỗi trang sản phẩm (sau khi đã được tạo ra lần đầu - dù là lúc build hay thông qua fallback) sẽ được kiểm tra và tái tạo trong nền sau mỗi 60 giây nếu có request.
Khi nào nên dùng ISR?
  • Các trang có nội dung thay đổi không quá thường xuyên nhưng bạn muốn dữ liệu tươi mới hơn so với SSG build-time.
  • Các trang chi tiết (sản phẩm, bài viết, tài liệu) trong các ứng dụng có lượng nội dung lớn, nơi việc rebuild toàn bộ là không khả thi.
  • Trang chủ hoặc các trang danh mục cần hiển thị dữ liệu cập nhật nhưng vẫn yêu cầu hiệu suất cao.
Khi nào có thể không cần dùng ISR?
  • Các trang có nội dung thay đổi rất thường xuyên, gần với thời gian thực (ví dụ: giá cổ phiếu, feed mạng xã hội, bảng điều khiển cá nhân). Lúc này SSR hoặc client-side fetching có thể phù hợp hơn.
  • Các trang có nội dung cực kỳ cá nhân hóa cho từng người dùng.
  • Các trang có nội dung không bao giờ thay đổi sau khi deploy (SSG truyền thống là đủ).

Comments

There are no comments at the moment.