Khi mới chuyển giao sang NextJS (đặc biệt là kiến trúc App Router), một trong những "cú sốc" phổ biến nhất đối với nhiều lập trình viên là tình trạng: Thực hiện thêm, sửa, xóa dữ liệu thành công trong Database, nhưng giao diện (UI) vẫn "trơ trơ" không chịu cập nhật.

Nhiều người lầm tưởng đây là lỗi logic code, nhưng thực chất, nguyên nhân cốt lõi đến từ cơ chế Caching cực kỳ mạnh mẽ và chủ động của NextJS. Kiến trúc này sinh ra để tối ưu hóa hiệu suất hiển thị và giảm tải cho máy chủ, nhưng nếu không hiểu rõ, nó sẽ trở thành "con dao hai lưỡi" gây ra lỗi Stale Data (dữ liệu cũ).

Dựa trên những kiến thức chuyên sâu từ khóa học NextJS 15 + TypeScript (và hoàn toàn sẵn sàng cho các phiên bản tương lai), dưới đây là "kim chỉ nam" giúp bạn làm chủ hệ thống Caching này.

Data Cache & Fetch API: Nghệ Thuật "Phá" Cache Chủ Động

Mặc định, NextJS sẽ tự động cache lại các request dùng fetch(). Điều này cực kỳ tuyệt vời cho tốc độ tải trang, nhưng lại là nguyên nhân chính khiến dữ liệu mới không được hiển thị.

Để giải quyết, bạn không nên tắt hoàn toàn cache (điều đó sẽ làm mất đi sức mạnh của NextJS), mà cần áp dụng kỹ thuật On-Demand Revalidation (Làm mới dữ liệu theo yêu cầu) ngay bên trong Server Action hoặc Route Handler sau khi thực hiện Mutation (thay đổi dữ liệu) thành công:

  • revalidatePath('/đường-dẫn'): Xóa toàn bộ cache của một trang cụ thể. Rất hữu ích khi bạn cập nhật dữ liệu và muốn trang hiển thị dữ liệu đó làm mới lại ngay lập tức.

  • revalidateTag('tên-tag'): Đây là phương pháp tinh tế và tối ưu hơn. Bạn gắn một "tag" cho lệnh fetch ban đầu, và khi dữ liệu thay đổi, bạn chỉ cần gọi tên tag đó. Mọi request có chứa tag này trên toàn bộ ứng dụng sẽ được tự động làm mới.

Kỷ Nguyên Mới Với Directive 'use cache' (NextJS 15)

Khóa học mang đến một cập nhật mang tính đột phá của hệ sinh thái NextJS 15: Directive 'use cache'.

Khác với việc cấu hình rườm rà trước đây, 'use cache' cho phép bạn kiểm soát chính xác mức độ lưu trữ bộ nhớ đệm (Static Rendering) ở cấp độ vi mô nhất. Bạn có thể đặt directive này ở:

  • Phạm vi File: Cache toàn bộ output của file đó.

  • Phạm vi Component: Chỉ cache phần UI của một component nhất định, giữ cho các phần khác vẫn dynamic.

  • Phạm vi Function: Cực kỳ mạnh mẽ khi bạn muốn tính toán các dữ liệu nặng và lưu lại kết quả để tái sử dụng mà không cần render lại toàn bộ component.

Tối Ưu Hóa Truy Vấn Database Với unstable_cache & React.cache

Không phải lúc nào chúng ta cũng lấy dữ liệu thông qua HTTP fetch(). Rất nhiều trường hợp bạn sẽ truy vấn trực tiếp vào Database thông qua ORM (như Prisma, Drizzle). Lúc này, cơ chế Data Cache mặc định của fetch sẽ không có tác dụng.

  • React.cache: Giúp bạn deduplicate (loại bỏ trùng lặp) các lời gọi hàm trong cùng một vòng đời render (per-request). Ví dụ: 3 component cùng gọi một hàm lấy chi tiết User, React.cache sẽ đảm bảo Database chỉ bị query đúng 1 lần.

  • unstable_cache: Cho phép bạn bọc các hàm gọi Database (hoặc các logic tính toán nặng) và đưa kết quả của chúng vào Data Cache của NextJS. Kết hợp công cụ này với cơ chế gắn thẻ (tags), bạn có thể dễ dàng gọi revalidateTag để làm mới dữ liệu từ Prisma giống hệt như cách bạn làm với fetch.

Kinh Nghiệm Rút Ra: Chiến Lược "Gắn Thẻ" (Tagging)

Chìa khóa để xử lý triệt để lỗi "Stale Data" không nằm ở việc bạn dùng hàm nào, mà nằm ở Tư duy kiến trúc Cache.

Việc phân loại dữ liệu và gắn "tags" (cache tags) đúng cách ngay từ khâu thiết kế API/Truy vấn là vô cùng quan trọng. Một khi hệ thống tags được quy hoạch chuẩn xác (ví dụ: ['products'], ['product', 'id-123']), việc gọi revalidate khi có sự kiện Mutation sẽ trở nên cực kỳ nhàn rỗi, đảm bảo dữ liệu đến tay người dùng luôn là phiên bản mới nhất với độ trễ (latency) thấp nhất!