Cache Components trong Next.js 16 năm 2026: Hướng dẫn chi tiết use cache, PPR và câu hỏi phỏng vấn

Phân tích chuyên sâu về Cache Components trong Next.js 16: directive use cache ở ba phạm vi, Partial Pre-Rendering, cacheLife profiles, cacheTag, bảo mật cache và câu hỏi phỏng vấn thường gặp cho developer năm 2026.

Cache Components trong Next.js 16: use cache, PPR và câu hỏi phỏng vấn

Next.js 16 Cache Components đánh dấu bước chuyển đổi quan trọng nhất trong cách Next.js xử lý caching kể từ khi App Router được giới thiệu. Mô hình cũ mặc định cache mọi thứ và yêu cầu opt-out khi cần. Mô hình mới trong Next.js 16 không cache bất cứ thứ gì theo mặc định và yêu cầu opt-in thông qua directive "use cache".

Sự thay đổi này mang lại quyền kiểm soát chi tiết hơn cho developers, đồng thời giải quyết nhiều vấn đề về bảo mật và độ tươi mới của dữ liệu mà mô hình cũ gặp phải. Bài viết này phân tích toàn diện về Cache Components, Partial Pre-Rendering và các câu hỏi phỏng vấn thường gặp liên quan đến chủ đề này.

Thay đổi tư duy cốt lõi

Next.js 16 chuyển từ caching ngầm định (mọi thứ được cache, opt-out bằng dynamic APIs) sang caching tường minh (không cache gì cả, opt-in bằng "use cache"). Thay đổi này ảnh hưởng đến routing, data fetching, rendering và cách các câu hỏi phỏng vấn được đặt ra.

Tại sao Next.js 16 thay thế Implicit Caching

Trong Next.js 15 và các phiên bản trước đó, fetch requests được cache mặc định ở cấp độ request. Route handlers và Server Components cũng được cache theo cách tương tự. Điều này dẫn đến một số vấn đề nghiêm trọng trong thực tế:

Dữ liệu cũ không mong muốn: Developers thường quên opt-out khỏi caching, dẫn đến việc người dùng nhìn thấy dữ liệu đã lỗi thời. Đặc biệt với các ứng dụng có dữ liệu thay đổi thường xuyên như dashboard hoặc trang quản trị.

Rủi ro bảo mật tiềm ẩn: Cache được chia sẻ giữa các users có thể vô tình expose dữ liệu nhạy cảm nếu không được cấu hình cẩn thận.

Khó debug: Khi dữ liệu không cập nhật như mong đợi, việc xác định nguyên nhân từ caching behavior ngầm định trở nên phức tạp.

Next.js 16 giải quyết những vấn đề này bằng cách đảo ngược mô hình: không có gì được cache trừ khi developer chủ động yêu cầu. Đây là triết lý "explicit over implicit" được áp dụng triệt để.

Directive use cache hoạt động ở ba phạm vi

Directive "use cache" có thể được áp dụng ở ba cấp độ khác nhau, mỗi cấp độ phục vụ các use cases riêng biệt.

Cấp độ File

Khi đặt "use cache" ở đầu file, toàn bộ module được cache như một đơn vị. Đây là lựa chọn phù hợp cho các trang tĩnh hoặc các trang hiếm khi thay đổi.

app/pricing/page.tsxtypescript
"use cache"

import { getPricingPlans } from "@/lib/data"

// Entire page is cached as a static shell
export default async function PricingPage() {
  const plans = await getPricingPlans()
  return (
    <section>
      {plans.map((plan) => (
        <PricingCard key={plan.id} plan={plan} />
      ))}
    </section>
  )
}

Cấp độ Component

Component-level caching cho phép cache từng component riêng lẻ. Props của component tự động trở thành một phần của cache key, đảm bảo mỗi tổ hợp props có cache entry riêng.

components/ProductRecommendations.tsxtsx
async function ProductRecommendations({ categoryId }: { categoryId: string }) {
  "use cache"
  // categoryId becomes part of the automatic cache key
  const products = await getTopProducts(categoryId)
  return (
    <ul>
      {products.map((p) => (
        <li key={p.id}>{p.name} - {p.price}</li>
      ))}
    </ul>
  )
}

Cấp độ Function

Function-level caching linh hoạt nhất, cho phép cache kết quả của các hàm fetch dữ liệu. Tham số của hàm tự động được bao gồm trong cache key.

lib/data.tstypescript
import { cacheLife } from "next/cache"

export async function getArticleBySlug(slug: string) {
  "use cache"
  cacheLife("hours")
  // slug is automatically included in the cache key
  const article = await db.article.findUnique({ where: { slug } })
  return article
}
Mẹo tối ưu

Ưu tiên function-level caching cho các data fetching functions để tái sử dụng cache across multiple components và pages.

Partial Pre-Rendering: Static Shell với Dynamic Holes

Partial Pre-Rendering (PPR) là tính năng đột phá trong Next.js 16, cho phép kết hợp static và dynamic content trong cùng một page một cách thông minh. Thay vì phải chọn giữa fully static hoặc fully dynamic, PPR cho phép pre-render một "static shell" và stream dynamic content vào các "holes" được đánh dấu bởi Suspense boundaries.

app/dashboard/page.tsxtsx
import { Suspense } from "react"
import { UserGreeting } from "@/components/UserGreeting"
import { StaticSidebar } from "@/components/StaticSidebar"
import { RecentActivity } from "@/components/RecentActivity"

export default function DashboardPage() {
  return (
    <div className="grid grid-cols-12 gap-6">
      {/* Cached static shell - served instantly */}
      <StaticSidebar />

      <main className="col-span-9">
        {/* Dynamic - streams in after static shell */}
        <Suspense fallback={<GreetingSkeleton />}>
          <UserGreeting />
        </Suspense>

        {/* Dynamic - streams independently */}
        <Suspense fallback={<ActivitySkeleton />}>
          <RecentActivity />
        </Suspense>
      </main>
    </div>
  )
}

Trong ví dụ trên, StaticSidebar được serve ngay lập tức từ cache, trong khi UserGreetingRecentActivity được stream độc lập khi dữ liệu sẵn sàng. Người dùng thấy layout chính ngay lập tức, cải thiện đáng kể perceived performance.

PPR hoạt động bằng cách:

  1. Pre-render static shell tại build time
  2. Serve static shell ngay lập tức khi có request
  3. Stream dynamic content vào các Suspense boundaries song song
  4. Mỗi dynamic section hoàn thành độc lập, không block lẫn nhau
Kích hoạt Cache Components

Thêm cacheComponents: true vào next.config.ts. Flag duy nhất này kích hoạt PPR, directive "use cache" và toàn bộ hệ thống Cache Components. Không cần cấu hình thêm.

cacheLife Profiles: Thay thế revalidate

Next.js 16 giới thiệu cacheLife để thay thế option revalidate trong fetch. Thay vì chỉ định số giây cứng nhắc, developers sử dụng semantic profiles mô tả ý định caching.

lib/data.tstypescript
import { cacheLife } from "next/cache"

export async function getExchangeRates() {
  "use cache"
  cacheLife("minutes") // Revalidates every few minutes
  const rates = await fetch("https://api.exchangerate.host/latest")
  return rates.json()
}

export async function getCompanyInfo() {
  "use cache"
  cacheLife("weeks") // Rarely changes
  return db.company.findFirst()
}

Các built-in profiles bao gồm: "seconds", "minutes", "hours", "days", "weeks", và "max" (cache vĩnh viễn cho đến khi invalidate thủ công).

Custom cacheLife Profiles

Đối với các yêu cầu cụ thể, developers có thể định nghĩa custom profiles trong next.config.ts:

next.config.tstypescript
import type { NextConfig } from "next"

const config: NextConfig = {
  cacheComponents: true,
  cacheLife: {
    // Custom profile for product data
    product: {
      stale: 300,      // Serve stale for 5 minutes
      revalidate: 3600, // Revalidate in background every hour
      expire: 86400,    // Hard expire after 24 hours
    },
  },
}

export default config

Conditional cacheLife

Một tính năng mạnh mẽ là khả năng áp dụng cacheLife có điều kiện dựa trên kết quả của data fetching:

typescript
export async function getProduct(id: string) {
  "use cache"
  const product = await db.product.findUnique({ where: { id } })
  if (!product) {
    // Short cache for missing items (may appear soon)
    cacheLife("minutes")
    return null
  }
  // Longer cache for existing products
  cacheLife("product")
  return product
}

Sẵn sàng chinh phục phỏng vấn React / Next.js?

Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.

cacheTag cho Targeted Invalidation

Trong khi cacheLife kiểm soát thời gian cache tồn tại, cacheTag cho phép invalidate cache một cách có chủ đích khi dữ liệu thay đổi. Đây là công cụ quan trọng cho các ứng dụng có dữ liệu được cập nhật thông qua user actions.

lib/data.tstypescript
import { cacheLife, cacheTag } from "next/cache"

export async function getProductById(id: string) {
  "use cache"
  cacheTag(`product-${id}`, "products")
  cacheLife("days")
  return db.product.findUnique({ where: { id } })
}

Khi sản phẩm được cập nhật, sử dụng revalidateTag trong Server Action để invalidate cache liên quan:

app/actions.tstypescript
"use server"

import { revalidateTag } from "next/cache"

export async function updateProduct(id: string, data: ProductUpdate) {
  await db.product.update({ where: { id }, data })
  // Invalidate this specific product AND the product list
  revalidateTag(`product-${id}`)
  revalidateTag("products")
}
Lưu ý quan trọng về Tag

Một cached function không có cacheTag() chỉ có thể hết hạn theo thời gian. Invalidation theo yêu cầu là không thể. Đây là lỗi thường gặp trong quá trình phát triển ban đầu và khó phát hiện cho đến khi dữ liệu cũ xuất hiện trên production.

Bảo mật: use cache và use cache private

Một trong những cải tiến bảo mật quan trọng nhất trong Next.js 16 là sự phân biệt rõ ràng giữa shared cache và private cache. Việc sử dụng sai directive có thể dẫn đến data leaks nghiêm trọng.

typescript
// WRONG: User data in shared cache - data leak risk
export async function getUserDashboard(userId: string) {
  "use cache"
  return db.user.findUnique({
    where: { id: userId },
    include: { orders: true, preferences: true },
  })
}

// CORRECT: Private cache scoped to the current user
export async function getUserDashboard() {
  "use cache: private"
  cacheLife("minutes")
  const session = await cookies()
  const userId = session.get("userId")?.value
  return db.user.findUnique({
    where: { id: userId },
    include: { orders: true, preferences: true },
  })
}

Sự khác biệt quan trọng:

| Directive | Phạm vi | Sử dụng khi | |-----------|---------|-------------| | "use cache" | Chia sẻ, tất cả người dùng | Dữ liệu công khai: bảng giá, bài viết, danh mục sản phẩm | | "use cache: private" | Phiên người dùng riêng | Dữ liệu cá nhân: dashboard, cài đặt, lịch sử đơn hàng | | "use cache: remote" | Chia sẻ, lưu trữ bên ngoài | Dữ liệu truy cập cao trong môi trường serverless |

Nguyên tắc bảo mật

Bất cứ khi nào cache dữ liệu phụ thuộc vào user session hoặc chứa thông tin cá nhân, BẮT BUỘC sử dụng "use cache: private". Việc quên điều này có thể expose dữ liệu của user A cho user B.

Migration từ Next.js 15 sang Cache Components

Quá trình migration đòi hỏi sự cẩn thận vì behavior mặc định đã thay đổi hoàn toàn. Có ba pitfalls chính cần lưu ý.

Pitfall 1: Closures trên request-scoped data. Nếu cached function tham chiếu đến giá trị từ scope bao quanh mà thay đổi theo request (header, cookie, user ID), việc chuyển đổi không đơn giản. Cần truyền giá trị đó làm argument tường minh hoặc sử dụng "use cache: private".

Pitfall 2: Conditional caching. Code trước đó wrap unstable_cache chỉ khi một điều kiện thỏa mãn cần được tái cấu trúc. "use cache" luôn hoạt động khi được áp dụng. Di chuyển điều kiện ra ngoài cached function.

Pitfall 3: Manual cache keys. unstable_cache yêu cầu mảng keyParts tường minh. Compiler "use cache" tự động tạo keys từ arguments cộng với build ID và function hash. Loại bỏ quản lý key thủ công là mục tiêu, nhưng cần xác minh rằng tất cả giá trị phân biệt cache đều là tham số function thực tế.

Câu hỏi phỏng vấn Next.js 16 Cache Components

Dưới đây là các câu hỏi phỏng vấn thường gặp về cache components next.js và partial pre-rendering, cùng với câu trả lời chi tiết.

Câu hỏi 1: Sự khác biệt giữa caching model trong Next.js 15 và Next.js 16 là gì?

Next.js 15 sử dụng implicit caching - mọi thứ được cache mặc định và developers phải opt-out. Next.js 16 sử dụng explicit caching - không có gì được cache mặc định và developers phải opt-in bằng "use cache" directive. Sự thay đổi này giúp tránh dữ liệu cũ không mong muốn và giảm rủi ro bảo mật từ shared cache.

Câu hỏi 2: Giải thích Partial Pre-Rendering hoạt động như thế nào?

PPR cho phép pre-render một static shell tại build time và stream dynamic content vào các "holes" được đánh dấu bởi Suspense boundaries tại request time. Static shell được serve ngay lập tức, cải thiện Time to First Byte, trong khi dynamic content streams độc lập. Điều này kết hợp ưu điểm của static generation với tính linh hoạt của server-side rendering.

Câu hỏi 3: Khi nào nên sử dụng "use cache: private" thay vì "use cache"?

Sử dụng "use cache: private" khi cache dữ liệu phụ thuộc vào user session hoặc chứa thông tin cá nhân như dashboard data, user preferences, order history. Cache này được scope riêng cho từng user. Sử dụng "use cache" cho dữ liệu công khai được chia sẻ giữa tất cả users như pricing plans, blog posts, product catalogs.

Câu hỏi 4: cacheLife profiles khác với revalidate option như thế nào?

revalidate chỉ định số giây cố định để cache tồn tại. cacheLife sử dụng semantic profiles như "minutes", "hours", "days" mô tả ý định caching, dễ đọc và maintain hơn. cacheLife cũng hỗ trợ custom profiles với ba tham số: stale (serve stale content), revalidate (background revalidation), và expire (hard expiration).

Câu hỏi 5: Làm thế nào để invalidate cache một cách có chủ đích trong Next.js 16?

Sử dụng cacheTag để gắn tags cho cached data và revalidateTag trong Server Actions để invalidate. Ví dụ, gắn tag product-${id}products cho product data, sau đó gọi revalidateTag("product-123") khi product được update. Điều này cho phép invalidation chính xác mà không ảnh hưởng đến cache không liên quan.

Câu hỏi 6: Tại sao caching model mới được coi là an toàn hơn?

Mô hình mới an toàn hơn vì: (1) Không có gì được cache mặc định, tránh vô tình cache dữ liệu nhạy cảm; (2) Phân biệt rõ ràng giữa shared cache và private cache thông qua directives; (3) Developers phải chủ động quyết định cache gì, buộc suy nghĩ về data sensitivity; (4) Private cache được scope theo user session, ngăn chặn data leaks giữa users.

Để tìm hiểu thêm về các chủ đề liên quan, tham khảo câu hỏi phỏng vấn Next.js data fetchingmodule Next.js Server Actions.

Checklist thực hành cho Cache Components

  • Kích hoạt cacheComponents: true trong next.config.ts và xóa các flag experimental.ppr hoặc experimental.dynamicIO
  • Audit mọi page: thêm "use cache" cho các trang tĩnh và data-fetching functions phục vụ nội dung công khai
  • Wrap tất cả dynamic content (user-specific, request-time) trong <Suspense> boundaries với skeleton fallbacks có ý nghĩa
  • Sử dụng "use cache: private" cho mọi function truy cập cookies, headers hoặc trả về dữ liệu cá nhân
  • Định nghĩa custom cacheLife profiles cho các danh mục dữ liệu phổ biến (product data, user sessions, static content)
  • Thêm cacheTag() vào mọi cached function có thể cần invalidation theo yêu cầu
  • Test trong production mode với next build && next start vì caching behavior trong next dev khác biệt đáng kể
  • Monitor cache-hit ratios theo page trong 24 giờ trước khi triển khai rộng

Bắt đầu luyện tập!

Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.

Kết luận

  • Next.js 16 thay thế implicit caching bằng "use cache" tường minh ở cấp độ file, component và function
  • PPR đã stable và mặc định dưới cacheComponents: true, cung cấp static shells với dynamic content được stream
  • cacheLife profiles thay thế revalidate với kiểm soát thời gian cache ba chiều được tập trung
  • cacheTag + revalidateTag cho phép invalidation theo yêu cầu; thiếu tags đồng nghĩa chỉ có time-based expiry
  • "use cache: private" là bắt buộc cho dữ liệu user-specific để ngăn chặn data leaks giữa users
  • Câu hỏi phỏng vấn năm 2026 tập trung vào sự chuyển đổi implicit-to-explicit, luồng rendering PPR, bảo mật cache và các pitfalls khi migration từ Next.js 15

Thẻ

#next.js
#react
#caching
#ppr
#interview

Chia sẻ

Bài viết liên quan