•11 min read

Giải mã bộ nhớ đệm của Next.js 14 App Router: Gỡ lỗi dữ liệu cũ

Giải mã bộ nhớ đệm của Next.js 14 App Router: Gỡ lỗi dữ liệu cũ

TL;DR: Next.js App Router mặc định lưu trữ dữ liệu rất mạnh mẽ. Điều này rất tốt cho hiệu suất nhưng lại gây khó chịu khi dữ liệu của bạn không được cập nhật. Bạn cần hiểu bốn lớp bộ nhớ đệm: Request Memoization, Data Cache, Full Route Cache và Router Cache để kiểm soát luồng dữ liệu của ứng dụng một cách có thể dự đoán được.

Tổng quan kiến trúc bộ nhớ đệm của Next.js

Nếu bạn đã từng cập nhật cơ sở dữ liệu nhưng ứng dụng Next.js của bạn vẫn hiển thị dữ liệu cũ, bạn không đơn độc đâu. Hầu hết các nhà phát triển tiếp cận việc lưu trữ dữ liệu một cách phản ứng: cái gì chậm thì thêm bộ nhớ đệm; cái gì cũ thì xóa đi. Điều này không hiệu quả trong Next.js vì có nhiều bộ nhớ đệm hoạt động đồng thời, thường là không biết về nhau.

Hãy cùng phân tích các lớp này, cách kiểm soát chúng và cách ngừng "đấu tranh" với framework.

Audio Briefing
0:00 / 0:00

Lớp 1: Request Memoization

Request Memoization là bộ nhớ đệm bị hiểu lầm nhiều nhất trong Next.js vì nó trông giống như một bộ nhớ đệm dữ liệu bền vững, nhưng thực ra không phải. Trong một lần render máy chủ duy nhất, nếu bạn gọi fetch() với cùng một URL và các tùy chọn nhiều lần trên các component khác nhau, Next.js sẽ loại bỏ các bản sao thành một yêu cầu HTTP duy nhất.

Trực quan hóa Request Memoization

Điều này chỉ tồn tại trong suốt thời gian của một yêu cầu duy nhất. Nó không tồn tại qua nhiều người dùng hoặc tải lại trang. Đây là điều cho phép bạn tìm nạp dữ liệu trong Layouts và Pages của mình một cách độc lập mà không phải lo lắng về các truy vấn N+1.

// Both components call the same URL: only ONE HTTP request is made
async function Header() {
  const user = await fetch('https://api.example.com/me').then(r => r.json());
  return <header>Welcome, {user.name}</header>;
}

async function Sidebar() {
  const user = await fetch('https://api.example.com/me').then(r => r.json());
  return <aside>Profile: {user.avatar}</aside>;
}

Nếu bạn không sử dụng fetch() (ví dụ: gọi trực tiếp cơ sở dữ liệu bằng Prisma hoặc Drizzle), bạn phải tự ghi nhớ các truy vấn của mình bằng cách sử dụng hàm cache() của React để đạt được việc loại bỏ trùng lặp tương tự.

Advertisement

Lớp 2: Bộ nhớ đệm dữ liệu bền vững

Data Cache là thứ khiến các nhà phát triển bất ngờ khi chuyển từ Pages router. Theo mặc định, Next.js App Router lưu trữ tất cả các lệnh gọi fetch() vô thời hạn trên máy chủ. Điều này tồn tại qua các yêu cầu và triển khai trừ khi bị vô hiệu hóa rõ ràng.

Chiến lược lưu trữ dữ liệu bền vững

Nếu bạn đang xây dựng một bảng điều khiển cần dữ liệu thời gian thực, bạn phải từ chối hành vi này. Bạn có thể thực hiện điều này một cách chi tiết cho mỗi lần tìm nạp bằng cách sử dụng no-store.

// Opt out of caching entirely for real-time data
const liveData = await fetch('https://api.example.com/metrics', {
  cache: 'no-store'
});

Đối với dữ liệu cập nhật định kỳ, hãy sử dụng Tái tạo tĩnh tăng dần (ISR) bằng cách xác định khoảng thời gian tái xác thực.

// Cache data but refresh it in the background every 60 seconds
const cachedData = await fetch('https://api.example.com/stats', {
  next: { revalidate: 60 }
});

Các ứng dụng doanh nghiệp thường dựa vào Tái xác thực theo yêu cầu. Bạn lưu trữ dữ liệu vô thời hạn (force-cache) nhưng kích hoạt tái xác thực qua Webhooks khi CMS hoặc cơ sở dữ liệu cập nhật. Điều này đảm bảo cập nhật tức thì mà không lãng phí tài nguyên nền. Sử dụng revalidatePath('/route') hoặc revalidateTag('my-tag') trong Server Actions hoặc Route Handlers của bạn để xóa các mục bộ nhớ đệm cụ thể.

Lớp 3 & 4: Route và Router Cache

Full Route Cache lưu trữ payload HTML và React Server Component (RSC) cho các tuyến tĩnh tại thời điểm build. Khi người dùng truy cập tuyến, máy chủ ngay lập tức trả về payload đã lưu trữ thay vì render lại. Bộ nhớ đệm này tự động bị vô hiệu hóa khi bạn tái xác thực dữ liệu bằng cách sử dụng revalidatePath hoặc revalidateTag.

Bộ nhớ đệm Router phía máy khách

Router Cache là một bộ nhớ đệm trong bộ nhớ phía máy khách. Khi người dùng điều hướng ứng dụng của bạn, Next.js lưu trữ các phân đoạn tuyến đã truy cập. Điều này có nghĩa là việc quay lại một trang đã truy cập trước đó là tức thì. Tuy nhiên, nó cũng có nghĩa là các thay đổi phía máy khách có thể không phản ánh ngay lập tức trừ khi bạn gọi rõ ràng router.refresh() để buộc trình duyệt tìm nạp payload RSC mới nhất từ máy chủ.

Hiểu cách bốn lớp này giao thoa sẽ giúp bạn kiểm soát hoàn toàn hiệu suất và độ tươi mới của dữ liệu. Đừng đoán xem bộ nhớ đệm nào đã cũ, hãy xác định rõ ràng các chiến lược dữ liệu của bạn và để Next.js xử lý phần việc nặng nhọc.

Đọc thêm: Nếu bạn đang gặp khó khăn với SSR, hãy xem hướng dẫn này về cách khắc phục Lỗi Hydration của Next.js.

Tìm hiểu sâu: Cơ chế cốt lõi

Khi chúng ta nhìn sâu hơn, các cơ chế cơ bản cho thấy một sự tương tác phức tạp của các hệ thống. Trong phát triển hiện đại, việc hiểu các cơ chế này là điều phân biệt một người mới với một chuyên gia.

Hãy xem xét ví dụ thực tế này:

// A comprehensive example demonstrating advanced patterns
class ServiceManager {
  constructor() {
    this.services = new Map();
    this.initialized = false;
  }

  register(name, service) {
    if (this.services.has(name)) {
      throw new Error(`Service ${name} already registered`);
    }
    this.services.set(name, service);
  }

  async initializeAll() {
    this.initialized = true;
    for (const [name, service] of this.services) {
      if (typeof service.init === 'function') {
        await service.init();
      }
    }
  }

  get(name) {
    if (!this.initialized) {
      console.warn('Accessing services before initialization');
    }
    return this.services.get(name);
  }
}

Mô hình này đảm bảo rằng kiến trúc của chúng ta vẫn có thể mở rộng và mạnh mẽ ngay cả khi các yêu cầu kinh doanh thay đổi. Đó là một cách tiếp cận cơ bản mang lại lợi ích trong các ứng dụng quy mô lớn.

Advertisement

Ứng dụng thực tế và mở rộng quy mô

Việc triển khai điều này trong môi trường sản xuất đặt ra một loạt thách thức mới. Chúng ta phải tính đến tính đồng thời, quản lý trạng thái và rò rỉ bộ nhớ.

Ví dụ, khi xử lý các hệ thống có thông lượng cao, mọi tối ưu hóa nhỏ đều có giá trị. Chúng ta thường dựa vào các công cụ phân tích hiệu suất để xác định các nút thắt cổ chai không rõ ràng trong quá trình phát triển cục bộ.

Sơ đồ trên minh họa một chiến lược triển khai điển hình nơi ứng dụng của chúng ta mở rộng theo chiều ngang.

Kiểm tra sự hiểu biết của bạn

Bạn cũng có thể thích

Câu hỏi thường gặp

Next.js App Router vận hành bốn cơ chế bộ nhớ đệm riêng biệt: (1) Request Memoization (React loại bỏ trùng lặp các URL tìm nạp giống hệt nhau trong một lần render máy chủ duy nhất); (2) Data Cache (bộ nhớ đệm tìm nạp HTTP bền vững giữa các yêu cầu và triển khai trên máy chủ); (3) Full Route Cache (payload HTML và React Server Component tĩnh được lưu trữ tại thời điểm build hoặc tái xác thực); và (4) Client-side Router Cache (payload RSC trong bộ nhớ được lưu trữ trong phiên trình duyệt của người dùng).
Dữ liệu cũ thường xảy ra vì fetch() mặc định là { cache: 'force-cache' } trong Data Cache, hoặc vì Full Route Cache là tĩnh. Để khắc phục điều này: gọi revalidatePath('/your-route') hoặc revalidateTag('your-tag') bên trong Server Action của bạn sau khi thay đổi dữ liệu, hoặc chỉ định { next: { revalidate: 0 } } hoặc { cache: 'no-store' } cho các yêu cầu động, luôn mới.
revalidatePath xóa tất cả các yêu cầu tìm nạp đã lưu trữ và HTML được render trước cho một đường dẫn URL cụ thể (và tùy chọn các đường dẫn con của nó). revalidateTag chỉ xóa các lệnh gọi tìm nạp cụ thể trên toàn bộ ứng dụng đã được gắn thẻ với { next: { tags: ['my-tag'] } }, cho phép tái xác thực dữ liệu chi tiết trên nhiều trang độc lập đồng thời.
Router Cache lưu trữ các payload React Server Component đã truy cập và tìm nạp trước đó trong bộ nhớ của trình duyệt trong suốt phiên tab của người dùng (30 giây cho các tuyến động, 5 phút cho các tuyến tĩnh). Để buộc router phía máy khách loại bỏ các layout đã lưu trữ và tìm nạp các component máy chủ mới, hãy gọi router.refresh() từ next/navigation.
Việc gọi các hàm động như cookies(), headers() hoặc đọc searchParams trong một trang sẽ loại bỏ tuyến đó khỏi Full Route Cache tĩnh tại thời điểm yêu cầu. Trang được coi là được render động trên mỗi yêu cầu, mặc dù các yêu cầu fetch() riêng lẻ bên trong trang vẫn có thể truy cập Data Cache bền vững trừ khi được cấu hình với no-store.

Hướng dẫn kiến trúc liên quan

Share this article:

Stay Updated

Get the latest posts delivered straight to your inbox.

Free Developer Utilities

Free In-Browser Developer Tools

Clean AI CLI logs, build cron expressions, decode JWTs, and calculate chmod permissions offline.

Explore Tools
Advertisement