Next.js 16 Cache Components năm 2026: use cache, PPR và Câu hỏi Phỏng vấn

Phân tích chuyên sâu về Next.js 16 Cache Components: directive use cache, Partial Pre-Rendering (PPR), cacheLife, cacheTag, và câu hỏi phỏng vấn thực tế cho developer senior.

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

Next.js 16 Cache Components đại diện cho sự thay đổi lớn nhất trong cách Next.js xử lý caching kể từ khi App Router được giới thiệu. Mô hình cũ cache mọi thứ theo mặc định và yêu cầu opt-out. Mô hình mới không cache gì theo mặc định và yêu cầu opt-in với directive "use cache". Với Next.js 16.3, phát hành vào tháng 8 năm 2026, Cache Components có thêm Instant Navigations: bộ công cụ mang lại khả năng phản hồi như SPA cho các ứng dụng server-driven.

Thay đổi mô hình tư duy cốt lõi

Next.js 16 chuyển từ caching ngầm định (mọi thứ được cache, opt-out với dynamic APIs) sang caching tường minh (không cache gì, opt-in với "use cache"). Next.js 16.3 xây dựng thêm với Partial Prefetching và Instant Navigations, khiến mô hình tường minh có cảm giác nhanh như single-page app.

Lý do Next.js 16 thay thế Caching Ngầm định

Mô hình caching ngầm định trong Next.js 14-15 gây ra vấn đề về tính dự đoán. Một lệnh fetch bên trong Server Component tự động được dedupe và cache, nhưng việc một trang là static hay dynamic phụ thuộc vào những API nào nó sử dụng. Debug hành vi cache yêu cầu hiểu nhiều lớp ẩn: fetch cache, full-route cache, và router cache.

Next.js 16 loại bỏ cả ba cache ngầm định này. Mọi trang render động tại thời điểm request trừ khi được đánh dấu rõ ràng với "use cache". Export revalidate không còn tồn tại. unstable_cache được thay thế bởi directive "use cache" được compiler nhận diện. Bài blog phát hành Next.js 16 chi tiết phạm vi đầy đủ của những thay đổi này.

Sự chuyển đổi này đánh đổi tối ưu hóa tự động lấy kiểm soát tường minh. Hiệu năng có thể giảm ban đầu với những ứng dụng dựa vào caching ngầm định, nhưng trải nghiệm debug cải thiện đáng kể: nội dung được cache là vì code khai báo như vậy, không phải vì heuristics của framework.

Cách Directive use cache Hoạt động ở Ba Phạm vi

Directive "use cache" hoạt động ở ba cấp độ: file, component, và function. Chọn đúng phạm vi là quyết định caching quan trọng nhất trong Next.js 16.

Caching cấp file đánh dấu mọi async export trong file là có thể cache. Phù hợp với các trang có nội dung hoàn toàn tĩnh và không có dữ liệu riêng của người dùng.

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>
  )
}

Caching cấp component cache các component riêng lẻ trong một trang. Điều này cho phép Partial Pre-Rendering: component được cache render vào static shell, trong khi các component dynamic khác stream vào tại thời điểm request.

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>
  )
}

Caching cấp function nhắm trực tiếp vào các function lấy dữ liệu. Thay thế pattern unstable_cache cũ.

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
}

Compiler tự động tạo cache key từ các argument của function. Không cần mảng keyParts thủ công, không cần workaround JSON.stringify. Argument phải serializable (string, number, plain object). Truyền class instance hoặc function làm argument sẽ phá vỡ serialization.

Instant Navigations trong Next.js 16.3

Next.js 16.3 giải quyết phê bình phổ biến nhất về Server Components: navigation cảm thấy chậm vì cần network roundtrip. Instant Navigations khắc phục bằng cách prefetch shell có thể tái sử dụng theo route, không phải theo link.

Bật Instant Navigations với hai flag trong next.config.ts:

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

const nextConfig: NextConfig = {
  cacheComponents: true,
  partialPrefetching: true,
}

export default nextConfig

Với partialPrefetching: true, Next.js trích xuất loading shell từ mỗi route và cache nó trên client. Khi người dùng click link, shell render ngay lập tức trong khi nội dung dynamic stream vào. Đây là pattern phản hồi tương tự mà single-page app sử dụng, nhưng không bỏ server-driven rendering.

Stream, Cache, hoặc Block

Với mỗi async operation trong một route, lựa chọn: Stream với <Suspense> (loading state tức thì), Cache với "use cache" (UI cached tức thì), hoặc Block với export const instant = false (chờ server). Hai lựa chọn đầu tạo ra instant navigations.

Navigation Inspector trong Next.js DevTools cho phép tạm dừng navigation tại shell để xem chính xác những gì được prefetch. Instant Insights tự động hiển thị các navigation chậm trong quá trình phát triển, biến chúng thành lỗi có thể hành động.

Partial Pre-Rendering với Partial Prefetching

Partial Pre-Rendering (PPR) ở dạng experimental trong Next.js 14-15. Trong Next.js 16, PPR đã stable và tích hợp trực tiếp vào Cache Components thông qua cacheComponents: true. Next.js 16.3 mở rộng với Partial Prefetching, thay đổi cách shell được gửi đến client.

Trước 16.3, Next.js gửi prefetch request cho mỗi link trong viewport. Với Partial Prefetching, nó prefetch một shell cho mỗi route. Hai mươi link chat trỏ đến /chat/[id] kích hoạt một prefetch, không phải hai mươi. Điều này giảm network overhead và làm chiến lược prefetch tương tự cách SPA code-split theo route.

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 - prefetched and served instantly */}
      <StaticSidebar />

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

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

Cây quyết định rendering: component với "use cache" trở thành phần của static shell. Component được bọc trong <Suspense> đọc cookies, headers, hoặc dữ liệu request-specific khác stream động. Để prefetch theo từng link ngoài shell, thêm <Link prefetch={true}> vào các link cụ thể.

cacheLife Profiles: Thay thế revalidate

Export revalidate từ Next.js 15 không còn. Thay vào đó, cacheLife() cung cấp các profile có tên kiểm soát thời lượng cache. Các profile có sẵn bao gồm seconds, minutes, hours, days, weeks, và max.

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()
}

Profile tùy chỉnh được định nghĩa 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

Tập trung profile trong cấu hình có nghĩa một thay đổi điều chỉnh caching trên toàn ứng dụng. Loại bỏ các giá trị revalidate: 3600 rải rác trong codebase Next.js 15.

Một quy tắc cần nhớ: cacheLife() chỉ được thực thi một lần cho mỗi lần gọi function. Caching có điều kiện chỉ hợp lệ nếu một nhánh duy nhất thực thi.

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 và updateTag cho Invalidation có Mục tiêu

Không có cacheTag(), một function được cache chỉ có thể hết hạn theo thời gian. Invalidation theo yêu cầu cần gắn tag cho các entry cache và gọi revalidateTag() hoặc updateTag() mới trong Server Action.

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 } })
}
app/actions.tstypescript
"use server"

import { updateTag } 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
  updateTag(`product-${id}`)
  updateTag("products")
}

Sự khác biệt giữa revalidateTag và updateTag: cả hai đều invalidate, nhưng updateTag là primitive được khuyến nghị trong Next.js 16.3, được thiết kế để hoạt động liền mạch với mô hình caching mới. Tag hỗ trợ đến 256 ký tự mỗi tag, tối đa 128 tag cho mỗi cache entry.

Bẫy thiếu Tag

Một function được cache 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ể. Điều này dễ bỏ sót trong quá trình phát triển ban đầu và đau đớn khi phát hiện khách hàng báo cáo dữ liệu cũ trên production.

Bảo mật: Các Biến thể use cache

Directive "use cache" mặc định tạo shared cache. Bất kỳ tổ hợp argument nào cũng tạo ra cache entry có thể phục vụ cho bất kỳ người dùng nào. Điều này đúng với dữ liệu công khai nhưng nguy hiểm cho nội dung cá nhân hóa.

"use cache: private" tạo cache theo từng người dùng bao gồm session hiện tại trong cache key. Có thể truy cập an toàn cookies() và headers() bên trong phạm vi được cache.

"use cache: remote" lưu cache trong external storage. Trong môi trường serverless (Vercel, AWS Lambda), cache in-memory mặc định bị mất khi cold start. Remote caching đảm bảo cache entry tồn tại qua các function instance, mặc dù cần network roundtrip và thường phát sinh phí platform.

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 },
  })
}

Ma trận quyết định cho interview:

DirectivePhạm viSử dụng Khi
"use cache"Chia sẻ, tất cả người dùngDữ liệu công khai: pricing, bài viết, catalog sản phẩm
"use cache: private"Theo session người dùngDữ liệu cá nhân: dashboard, cài đặt, lịch sử đơn hàng
"use cache: remote"Chia sẻ, external storageDữ liệu high-traffic trong môi trường serverless

Root Params trong Next.js 16.3

Next.js 16.3 giới thiệu root params, giải quyết prop-drilling quá mức cho các dynamic segment được định nghĩa phía trên root layout. Root params như [lang] thực tế là global và cần thiết xuyên suốt component tree.

app/[lang]/posts/[slug]/page.tsxtsx
import { lang } from "next/root-params"

export default async function PostPage(
  props: PageProps<"/[lang]/posts/[slug]">
) {
  const { slug } = await props.params
  const language = await lang()

  return (
    <article>
      <p>Language: {language}</p>
      <p>Post: {slug}</p>
    </article>
  )
}

Root params hoạt động bên trong phạm vi use cache, và chỉ những params thực sự được đọc mới trở thành phần của cache key. Điều này làm cho các pattern internationalization thuận tiện hơn mà không phá vỡ hành vi cache.

Testing Instant Navigations

Helper test instant() cho Playwright cho phép assert nội dung nào hiển thị ngay sau navigation, không cần chờ network. Bắt các regression khi refactoring vô tình làm navigation chậm.

e2e/instant-navigation.spec.tstypescript
import { expect, test } from "@playwright/test"
import { instant } from "@next/playwright"

test("product title is available immediately", async ({ page }) => {
  await page.goto("/products/shoes")

  // Assert what's visible without waiting for network
  await instant(page, async () => {
    await page.click('a[href="/products/hats"]')
    await expect(page.locator("h1")).toContainText("Baseball Cap")
    await expect(page.getByText("Checking inventory...")).toBeVisible()
  })

  await expect(page.getByText("12 in stock")).toBeVisible()
})

Pattern này đảm bảo các trang dự định load instant vẫn instant qua các thay đổi code. Navigation Inspector trong DevTools bổ sung bằng cách cho phép kiểm tra trực quan shell trong quá trình phát triển.

Câu hỏi Interview: Developer Senior Được Hỏi Gì

Những câu hỏi này phản ánh pattern interview thực tế năm 2026 cho các vị trí Next.js senior. Mỗi câu nhắm vào khía cạnh cụ thể của Cache Components và các cập nhật 16.3.

Q1: Giải thích sự chuyển đổi từ caching ngầm định sang tường minh trong Next.js 16. Tại sao framework thực hiện thay đổi này?

Next.js 14-15 cache các fetch call và trang một cách ngầm định. Debug xem trang là static hay dynamic yêu cầu trace qua nhiều lớp cache ẩn. Mô hình tường minh với "use cache" làm caching hiển thị trong source code. Đánh đổi: hiệu năng có thể giảm ban đầu với ứng dụng migrate từ caching ngầm định, nhưng developer có toàn quyền kiểm soát và dự đoán.

Q2: Ba phạm vi của "use cache" là gì và khi nào nên dùng mỗi loại?

Cấp file cho các trang hoàn toàn tĩnh. Cấp component cho việc kết hợp nội dung cached và dynamic trong một trang (pattern PPR). Cấp function cho việc cache các operation lấy dữ liệu cụ thể. Lựa chọn phạm vi quyết định độ chi tiết cache và ranh giới invalidation.

Q3: Partial Prefetching trong 16.3 khác với prefetching trong 16.0 như thế nào?

Trong 16.0, Next.js gửi prefetch request cho mỗi link trong viewport. Trong 16.3 với partialPrefetching: true, nó prefetch một shell có thể tái sử dụng cho mỗi route, cached trên client xuyên suốt session. Hai mươi link đến /chat/[id] kích hoạt một prefetch cho shell route chat, không phải hai mươi request riêng biệt. Giảm network overhead và tương đồng với cách SPA code-split.

Q4: Một team cache function trả về lịch sử đơn hàng người dùng với "use cache". Điều gì xảy ra?

Shared cache lưu kết quả với key dựa trên argument của function. Nếu function nhận parameter userId, các người dùng khác nhau có cache entry khác nhau, nhưng cache vẫn là infrastructure chia sẻ. Nếu function đọc userId từ cookies() thay vì parameter, build fail vì cookies() là runtime API bị cấm trong phạm vi shared cache. Giải pháp: chuyển sang "use cache: private" hoặc truyền user ID như argument tường minh.

Q5: cacheLife khác với export revalidate cũ như thế nào?

revalidate là một số đơn (giây) đặt ở cấp trang hoặc layout. cacheLife dùng profile có tên với ba chiều: stale (serve nội dung cũ), revalidate (khoảng thời gian refresh nền), và expire (hết hạn cứng). Profile tập trung trong next.config.ts, nên một thay đổi ảnh hưởng tất cả call site dùng profile đó.

Q6: Sự khác biệt giữa revalidateTag và updateTag là gì?

Cả hai đều invalidate cache entry theo tag. updateTag là primitive được khuyến nghị trong Next.js 16.3, được thiết kế để tích hợp sạch với mô hình caching tường minh. Trong thực tế, cả hai đều hoạt động cho invalidation theo yêu cầu, nhưng updateTag là API hướng tới tương lai.

Q7: Khi nào nên dùng export const instant = false?

Khi một route cố ý block navigation cho đến khi server phản hồi. Ví dụ, blog có thể không bao giờ hiện loading shell cho bài viết, muốn người đọc thấy nội dung đầy đủ ngay. Điều này loại trừ khỏi Instant Insights error cho route đó.

Để có thêm câu hỏi interview Next.js data fetching, SharpSkill cung cấp module luyện tập với các session có giới hạn thời gian và giải thích chi tiết. Module Next.js Server Actions bao gồm các pattern Server Action kết hợp với cache invalidation.

Checklist Thực tế cho Production Cache Components

  • Bật cacheComponents: true và partialPrefetching: true trong next.config.ts
  • Audit mỗi trang: thêm "use cache" vào các trang tĩnh và function lấy dữ liệu phục vụ nội dung công khai
  • Bọc tất cả nội dung dynamic (user-specific, request-time) trong <Suspense> boundary với skeleton fallback có ý nghĩa
  • Dùng "use cache: private" cho bất kỳ function nào truy cập cookies, headers, hoặc trả về dữ liệu cá nhân
  • Cân nhắc "use cache: remote" cho dữ liệu high-traffic trong môi trường serverless
  • Định nghĩa profile cacheLife tùy chỉnh cho các danh mục dữ liệu phổ biến (dữ liệu sản phẩm, user session, nội dung tĩnh)
  • Thêm cacheTag() vào mỗi function được cache có thể cần invalidation theo yêu cầu
  • Dùng updateTag() cho invalidation theo yêu cầu trong Server Actions
  • Test trong production mode với next build && next start vì hành vi caching trong next dev khác đáng kể
  • Viết Playwright test với instant() để bắt navigation regression
  • Dùng Navigation Inspector trong DevTools để visualize shell được prefetch

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.

Sources

Điều Cần Nhớ về Next.js 16 Cache Components

  • Next.js 16 thay thế caching ngầm định bằng "use cache" tường minh ở phạm vi file, component, và function
  • Next.js 16.3 thêm Instant Navigations: với partialPrefetching: true, ứng dụng cảm giác phản hồi như SPA
  • PPR stable dưới cacheComponents: true, phân phối static shell với dynamic content được stream
  • Profile cacheLife thay thế revalidate với kiểm soát thời lượng cache ba chiều tập trung
  • cacheTag + updateTag cho phép invalidation theo yêu cầu; thiếu tag có nghĩa chỉ hết hạn theo thời gian
  • "use cache: private" bắt buộc cho dữ liệu user-specific để ngăn rò rỉ dữ liệu giữa người dùng
  • "use cache: remote" giúp môi trường serverless duy trì cache qua cold start
  • Root params từ next/root-params giải quyết prop-drilling cho dynamic segment global như [lang]
  • Câu hỏi interview năm 2026 tập trung vào sự chuyển đổi ngầm định sang tường minh, Instant Navigations 16.3, Partial Prefetching, và bảo mật cache

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.

Thử thách hôm nay

Bạn có tìm ra lỗi trong React / Next.js không?

Một đoạn mã thật, một lỗi ẩn, mỗi ngày một lượt. Không cần tài khoản để thử.

Anthony Fillion-Maillet

Viết bởi

Anthony Fillion-Maillet

Người sáng lập SharpSkill

Lập trình viên fullstack hơn 10 năm. Anh điều hành SharpSkill và chịu trách nhiệm về mọi nội dung đăng tại đây.

Cập nhật ngày 25 tháng 8, 2026

Thẻ

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

Chia sẻ

Bài viết liên quan