Những gì Next.js cung cấp sẵn cho bạn

  • Data cache – Trong Next.js 13+, mỗi lời gọi fetch() sẽ được lưu vào bộ nhớ đệm (memory cache) theo từng yêu cầu. Việc thêm tùy chọn revalidate sẽ báo cho runtime làm mới dữ liệu sau một khoảng thời gian nhất định, giúp chuyển đổi một API call đang "hot" thành "cold" chỉ khi thực sự cần thiết.
  • Full-route cache – Framework này lưu trữ HTML đã render và dữ liệu cần thiết cho toàn bộ một route. Những người dùng quay lại sẽ thấy sự chuyển trang tức thì vì máy chủ bỏ qua bước render lại.
  • ISR – Các trang tĩnh sẽ được tái tạo (regenerate) dưới nền trong khi phiên bản cũ vẫn tiếp tục phục vụ người dùng. Điều này cho phép bạn duy trì một trang web lớn ở dạng tĩnh mà không cần build lại toàn bộ mỗi khi nội dung thay đổi.
  • Server Component cache() – Helper cache của React giúp memoize các tính toán tốn kém hoặc các kết nối cơ sở dữ liệu trong suốt thời gian của một yêu cầu duy nhất, tránh việc thực hiện lặp lại các công việc trong cây component của một trang.

Các bộ nhớ đệm này giải quyết được vấn đề “first-hit” nhưng vẫn nằm bên trong tiến trình Node. Để bảo vệ máy chủ và cơ sở dữ liệu, hãy đẩy việc caching ra bên ngoài.

Các lớp bên ngoài bạn có thể thêm vào

Lớp Lưu trữ gì Công cụ điển hình Giúp ích như thế nào
CDN Tài nguyên tĩnh, HTML, API JSON Cloudflare, Akamai, AWS CloudFront Đưa nội dung đến các vị trí edge, rút ngắn thời gian phản hồi (round-trip) tới người dùng
Reverse proxy Toàn bộ phản hồi của trang trước khi chúng đến Node Nginx, Varnish Phục vụ trực tiếp các trang đã được cache, giảm tải cho instance Next.js
Application-level cache Kết quả của các truy vấn DB hoặc API call tốn kém Redis, Memcached Cung cấp một kho lưu trữ key-value nhanh chóng, vẫn tồn tại sau khi khởi động lại máy chủ và có thể chia sẻ giữa nhiều instance ứng dụng

Mỗi lớp nằm càng xa cơ sở dữ liệu gốc, nên một lỗi cache miss ở một cấp độ sẽ dẫn đến cấp độ tiếp theo, và cuối cùng chỉ chạm đến cơ sở dữ liệu khi thực sự cần thiết.

Giữ cho cache luôn mới

Việc vô hiệu hóa cache (invalidation) thường khiến nhiều đội ngũ gặp khó khăn. Có ba mô hình thực tế hoạt động rất hiệu quả:

  • Time-based (TTL) – Gán một thời gian hết hạn cố định cho một mục cache. Dễ cấu hình nhưng có thể phục vụ dữ liệu cũ cho đến khi bộ đếm thời gian hết hạn.
  • Event-driven – Kết nối với CMS hoặc bất kỳ nguồn dữ liệu nào có gửi webhook khi nội dung thay đổi. Webhook này sẽ kích hoạt việc xóa (purge) mục cache liên quan.
  • Tag-based – Gắn một tag logic vào một nhóm các lệnh fetch (ví dụ: product-list). Khi bất kỳ mục nào trong nhóm đó thay đổi, chỉ cần một lời gọi revalidateTag('product-list') sẽ xóa mọi mục có chung tag đó.

Việc kết hợp các phương pháp này cho phép bạn cân bằng giữa độ tươi mới của dữ liệu và tỷ lệ cache-hit.

Hệ thống phân cấp hiệu quả cho hầu hết các đội ngũ

  1. Browser cache – Các tài nguyên tĩnh (CSS, JS, hình ảnh) được gán max-age dài để thiết bị của người dùng không cần yêu cầu mạng nữa.
  2. CDN – Các edge node cache toàn bộ trang HTML và API JSON, tuân thủ các header Cache-Control mà bạn thiết lập trong Next.js.
  3. Reverse proxy – Một instance Nginx hoặc Varnish nằm phía trước máy chủ Next.js, phục vụ các phản hồi đã được cache cho các route hiếm khi thay đổi.
  4. Next.js internal cache – Các bộ nhớ đệm dữ liệu và route của framework xử lý việc memoization theo từng yêu cầu và ISR.
  5. Application cache – Redis lưu trữ kết quả của các truy vấn cơ sở dữ liệu nặng, được định danh bằng các tham số truy vấn hoặc tag.
  6. Database – Nguồn sự thật cuối cùng (source of truth), chỉ được truy vấn khi xảy ra cache miss ở tất cả các cấp cao hơn.

Khi một yêu cầu đến, nó sẽ đi qua danh sách này cho đến khi có một lớp phản hồi. Câu trả lời nhanh nhất sẽ thắng, và phản hồi đó sẽ được ghi ngược lên chuỗi để phục vụ cho các lần truy cập sau.

Kết hợp các thành phần lại với nhau

  1. Thiết lập header Cache-Control trong các API route của bạn
  2. Cấu hình CDN của bạn – Bật edge caching cho các endpoint HTML và JSON, và đảm bảo CDN tuân thủ Cache-Control.
  3. Triển khai một reverse proxy – Đặt Nginx phía trước máy chủ Next.js của bạn.
  4. Thêm Redis – Cache kết quả của các truy vấn cơ sở dữ liệu tốn kém.
  5. Sử dụng revalidateTag trong các component của bạn – Gọi revalidateTag để xóa bộ nhớ đệm nội bộ của Next.js cho một tag cụ thể.

Bài học rút ra

Một bộ nhớ đệm duy nhất bên trong Next.js giúp tăng tốc yêu cầu đầu tiên, nhưng khả năng mở rộng thực sự đến từ một stack có kỷ luật: browser → CDN → reverse proxy → framework → Redis → database. Hãy cấu hình từng lớp, xử lý việc vô hiệu hóa bằng thời gian, sự kiện và tag, đồng thời theo dõi các chỉ số của bạn. Độ trễ sẽ luôn ở mức thấp trong khi backend của bạn vẫn trụ vững trước các đợt tăng đột biến về lưu lượng truy cập.