Movatterモバイル変換


[0]ホーム

URL:


Skip to content
Important
Security Advisory: React2Shell & two new vulnerabilities
Find out more

Cache Components

Last updated November 5, 2025

Good to know: Cache Components is an opt-in feature. Enable it by setting thecacheComponents flag totrue in your Next config file. SeeEnabling Cache Components for more details.

Cache Components lets you mix static, cached, and dynamic content in a single route, giving you the speed of static sites with the flexibility of dynamic rendering.

Server-rendered applications typically force a choice between static pages (fast but stale) and dynamic pages (fresh but slow). Moving this work to the client trades server load for larger bundles and slower initial rendering.

Cache Components eliminates these tradeoffs by prerendering routes into astatic HTML shell that's immediately sent to the browser, with dynamic content updating the UI as it becomes ready.

Partially re-rendered Product Page showing static nav and product information, and dynamic cart and recommended productsPartially re-rendered Product Page showing static nav and product information, and dynamic cart and recommended products

How rendering works with Cache Components

At build time, Next.js renders your route's component tree. As long as components don't access network resources, certain system APIs, or require an incoming request to render, their output isautomatically added to the static shell. Otherwise, you must choose how to handle them:

Because this happens ahead of time, before a request arrives, we refer to it as prerendering. This generates a static shell consisting of HTML for initial page loads and a serializedRSC Payload for client-side navigation, ensuring the browser receives fully rendered content instantly whether users navigate directly to the URL or transition from another page.

Next.js requires you to explicitly handle components that can't complete during prerendering. If they aren't wrapped in<Suspense> or marked withuse cache, you'll see anUncached data was accessed outside of <Suspense> error during development and build time.

Good to know: Caching can be applied at the component or function level, while fallback UI can be defined around any subtree, which means you can compose static, cached, and dynamic content within a single route.

Diagram showing partially rendered page on the client, with loading UI for chunks that are being streamed.Diagram showing partially rendered page on the client, with loading UI for chunks that are being streamed.

This rendering approach is calledPartial Prerendering, and it's the default behavior with Cache Components. For the rest of this document, we simply refer to it as "prerendering" which can produce a partial or complete output.

🎥 Watch: Why Partial Prerendering and how it works →YouTube (10 minutes).

Automatically prerendered content

Operations like synchronous I/O, module imports, and pure computations can complete during prerendering. Components using only these operations have their rendered output included in the static HTML shell.

Because all operations in thePage component below complete during rendering, its rendered output is automatically included in the static shell. When both the layout and page prerender successfully, the entire route is the static shell.

page.tsx
import fsfrom'node:fs'exportdefaultasyncfunctionPage() {// Synchronous file system readconstcontent=fs.readFileSync('./config.json','utf-8')// Module importsconstconstants=awaitimport('./constants.json')// Pure computationsconstprocessed=JSON.parse(content).items.map((item)=>item.value*2)return (    <div>      <h1>{constants.appName}</h1>      <ul>        {processed.map((value, i)=> (          <likey={i}>{value}</li>        ))}      </ul>    </div>  )}

Good to know: You can verify that a route was fully prerendered by checking the build output summary. Alternatively see what content was added to the static shell of any page by viewing the page source in your browser.

Defer rendering to request time

During prerendering, when Next.js encounters work it can't complete (like network requests, accessing request data, or async operations), it requires you to explicitly handle it. To defer rendering to request time, a parent component must provide fallback UI using a Suspense boundary. The fallback becomes part of the static shell while the actual content resolves at request time.

Place Suspense boundaries as close as possible to the components that need them. This maximizes the amount of content in the static shell, since everything outside the boundary can still prerender normally.

Good to know: With Suspense boundaries, multiple dynamic sections can render in parallel rather than blocking each other, reducing total load time.

Dynamic content

External systems provide content asynchronously, which often takes an unpredictable time to resolve and may even fail. This is why prerendering doesn't execute them automatically.

In general, when you need the latest data from the source on each request (like real-time feeds or personalized content), defer rendering by providing fallback UI with a Suspense boundary.

For example theDynamicContent component below uses multiple operations that are not automatically prerendered.

page.tsx
import { Suspense }from'react'import fsfrom'node:fs/promises'import { setTimeout }from'node:timers/promises'asyncfunctionDynamicContent() {// Network requestconstdata=awaitfetch('https://api.example.com/data')// Database queryconstusers=awaitdb.query('SELECT * FROM users')// Async file system operationconstfile=awaitfs.readFile('..','utf-8')// Simulating external system delayawaitsetTimeout(100)// from 'node:timers/promises'return <div>Not in the static shell</div>}

To useDynamicContent within a page, wrap it in<Suspense> to define fallback UI:

page.tsx
exportdefaultasyncfunctionPage(props) {return (    <>      <h1>Part of the static shell</h1>      {/* <p>Loading..</p> is part of the static shell */}      <Suspensefallback={<p>Loading..</p>}>        <DynamicContent />        <div>Sibling excluded from static shell</div>      </Suspense>    </>  )}

Prerendering stops at thefetch request. The request itself is not started, and any code after it is not executed.

The fallback (<p>Loading...</p>) is included in the static shell, while the component's content streams at request time.

In this example, since all operations (network request, database query, file read, and timeout) run sequentially within the same component, the content won't appear until they all complete.

Good to know: For dynamic content that doesn't change frequently, you can useuse cache to include the dynamic data in the static shell instead of streaming it. See theduring prerendering section for an example.

Runtime data

A specific type of dynamic data that requires request context, only available when a user makes a request.

page.tsx
import { cookies, headers }from'next/headers'import { Suspense }from'react'asyncfunctionRuntimeData({ searchParams }) {// Accessing request dataconstcookieStore=awaitcookies()constheaderStore=awaitheaders()constsearch=await searchParamsreturn <div>Not in the static shell</div>}

To use theRuntimeData component in, wrap it in a<Suspense> boundary:

page.tsx
exportdefaultasyncfunctionPage(props) {return (    <>      <h1>Part of the static shell</h1>      {/* <p>Loading..</p> is part of the static shell */}      <Suspensefallback={<p>Loading..</p>}>        <RuntimeDatasearchParams={props.searchParams} />        <div>Sibling excluded from static shell</div>      </Suspense>    </>  )}

Useconnection() if you need to defer to request time without accessing any of the runtime APIs above.

Good to know: Runtime data cannot be cached withuse cache because it requires request context. Components that access runtime APIs must always be wrapped in<Suspense>. However, you can extract values from runtime data and pass them as arguments to cached functions. See thewith runtime data section for an example.

Non-deterministic operations

Operations likeMath.random(),Date.now(), orcrypto.randomUUID() produce different values each time they execute. To ensure these run at request time (generating unique values per request), Cache Components requires you to explicitly signal this intent by calling these operations after dynamic or runtime data access.

import { connection }from'next/server'import { Suspense }from'react'asyncfunctionUniqueContent() {// Explicitly defer to request timeawaitconnection()// Non-deterministic operationsconstrandom=Math.random()constnow=Date.now()constdate=newDate()constuuid=crypto.randomUUID()constbytes=crypto.getRandomValues(newUint8Array(16))return (    <div>      <p>{random}</p>      <p>{now}</p>      <p>{date.getTime()}</p>      <p>{uuid}</p>      <p>{bytes}</p>    </div>  )}

Because theUniqueContent component defers to request time, to use it within a route, it must be wrapped in<Suspense>:

page.tsx
exportdefaultasyncfunctionPage() {return (// <p>Loading..</p> is part of the static shell    <Suspensefallback={<p>Loading..</p>}>      <UniqueContent />    </Suspense>  )}

Every incoming request would see different random numbers, date, etc.

Good to know: You can cache non-deterministic operations withuse cache. See thewith non-deterministic operations section for examples.

Usinguse cache

Theuse cache directive caches the return value of async functions and components. You can apply it at the function, component, or file level.

Arguments and any closed-over values from parent scopes automatically become part of thecache key, which means different inputs produce separate cache entries. This enables personalized or parameterized cached content.

Whendynamic content doesn't need to be fetched fresh from the source on every request, caching it lets you include the content in the static shell during prerendering, or reuse the result at runtime across multiple requests.

Cached content can be revalidated in two ways: automatically based on the cache lifetime, or on-demand using tags withrevalidateTag orupdateTag.

Good to know: Seeserialization requirements and constraints for details on what can be cached and how arguments work.

During prerendering

Whiledynamic content is fetched from external sources, it's often unlikely to change between accesses. Product catalog data updates with inventory changes, blog post content rarely changes after publishing, and analytics reports for past dates remain static.

If this data doesn't depend onruntime data, you can use theuse cache directive to include it in the static HTML shell. UsecacheLife to define how long to use the cached data.

When revalidation occurs, the static shell is updated with fresh content. SeeTagging and revalidating for details on on-demand revalidation.

app/page.tsx
import { cacheLife }from'next/cache'exportdefaultasyncfunctionPage() {'use cache'cacheLife('hours')constusers=awaitdb.query('SELECT * FROM users')return (    <ul>      {users.map((user)=> (        <likey={user.id}>{user.name}</li>      ))}    </ul>  )}

ThecacheLife function accepts a cache profile name (like'hours','days', or'weeks') or a custom configuration object to control cache behavior:

app/page.tsx
import { cacheLife }from'next/cache'exportdefaultasyncfunctionPage() {'use cache'cacheLife({    stale:3600,// 1 hour until considered stale    revalidate:7200,// 2 hours until revalidated    expire:86400,// 1 day until expired  })constusers=awaitdb.query('SELECT * FROM users')return (    <ul>      {users.map((user)=> (        <likey={user.id}>{user.name}</li>      ))}    </ul>  )}

See thecacheLife API reference for available profiles and custom configuration options.

With runtime data

Runtime data anduse cache cannot be used in the same scope. However, you can extract values from runtime APIs and pass them as arguments to cached functions.

app/profile/page.tsx
import { cookies }from'next/headers'import { Suspense }from'react'exportdefaultfunctionPage() {// Page itself creates the dynamic boundaryreturn (    <Suspensefallback={<div>Loading...</div>}>      <ProfileContent />    </Suspense>  )}// Component (not cached) reads runtime dataasyncfunctionProfileContent() {constsession= (awaitcookies()).get('session')?.valuereturn <CachedContentsessionId={session} />}// Cached component/function receives data as propsasyncfunctionCachedContent({ sessionId }: { sessionId:string }) {'use cache'// sessionId becomes part of cache keyconstdata=awaitfetchUserData(sessionId)return <div>{data}</div>}

At request time,CachedContent executes if no matching cache entry is found, and stores the result for future requests.

With non-deterministic operations

Within ause cache scope, non-deterministic operations execute during prerendering. This is useful when you want the same rendered output served to all users:

exportdefaultasyncfunctionPage() {'use cache'// Execute once, then cached for all requestsconstrandom=Math.random()constrandom2=Math.random()constnow=Date.now()constdate=newDate()constuuid=crypto.randomUUID()constbytes=crypto.getRandomValues(newUint8Array(16))return (    <div>      <p>        {random} and {random2}      </p>      <p>{now}</p>      <p>{date.getTime()}</p>      <p>{uuid}</p>      <p>{bytes}</p>    </div>  )}

All requests will be served a route containing the same random numbers, timestamp, and UUID until the cache is revalidated.

Tagging and revalidating

Tag cached data withcacheTag and revalidate it after mutations usingupdateTag in Server Actions for immediate updates, orrevalidateTag when delays in updates are acceptable.

WithupdateTag

UseupdateTag when you need to expire and immediately refresh cached data within the same request:

app/actions.ts
import { cacheTag, updateTag }from'next/cache'exportasyncfunctiongetCart() {'use cache'cacheTag('cart')// fetch data}exportasyncfunctionupdateCart(itemId:string) {'use server'// write data using the itemId// update the user cartupdateTag('cart')}

WithrevalidateTag

UserevalidateTag when you want to invalidate only properly tagged cached entries with stale-while-revalidate behavior. This is ideal for static content that can tolerate eventual consistency.

app/actions.ts
import { cacheTag, revalidateTag }from'next/cache'exportasyncfunctiongetPosts() {'use cache'cacheTag('posts')// fetch data}exportasyncfunctioncreatePost(post:FormData) {'use server'// write data using the FormDatarevalidateTag('posts','max')}

For more detailed explanation and usage examples, see theuse cache API reference.

What should I cache?

What you cache should be a function of what you want your UI loading states to be. If data doesn't depend on runtime data and you're okay with a cached value being served for multiple requests over a period of time, useuse cache withcacheLife to describe that behavior.

For content management systems with update mechanisms, consider using tags with longer cache durations and rely onrevalidateTag to mark static initial UI as ready for revalidation. This pattern allows you to serve fast, cached responses while still updating content when it actually changes, rather than expiring the cache preemptively.

Putting it all together

Here's a complete example showing static content, cached dynamic content, and streaming dynamic content working together on a single page:

app/blog/page.tsx
import { Suspense }from'react'import { cookies }from'next/headers'import { cacheLife }from'next/cache'import Linkfrom'next/link'exportdefaultfunctionBlogPage() {return (    <>      {/* Static content - prerendered automatically */}      <header>        <h1>Our Blog</h1>        <nav>          <Linkhref="/">Home</Link> | <Linkhref="/about">About</Link>        </nav>      </header>      {/* Cached dynamic content - included in the static shell */}      <BlogPosts />      {/* Runtime dynamic content - streams at request time */}      <Suspensefallback={<p>Loading your preferences...</p>}>        <UserPreferences />      </Suspense>    </>  )}// Everyone sees the sameblog posts (revalidated every hour)asyncfunctionBlogPosts() {'use cache'cacheLife('hours')constres=awaitfetch('https://api.vercel.app/blog')constposts=awaitres.json()return (    <section>      <h2>Latest Posts</h2>      <ul>        {posts.slice(0,5).map((post:any)=> (          <likey={post.id}>            <h3>{post.title}</h3>            <p>              By {post.author} on {post.date}            </p>          </li>        ))}      </ul>    </section>  )}// Personalized per user based on their cookieasyncfunctionUserPreferences() {consttheme= (awaitcookies()).get('theme')?.value||'light'constfavoriteCategory= (awaitcookies()).get('category')?.valuereturn (    <aside>      <p>Your theme: {theme}</p>      {favoriteCategory&& <p>Favorite category: {favoriteCategory}</p>}    </aside>  )}

During prerendering the header (static) and the blog posts fetched from the API (cached withuse cache), both become part of the static shell along with the fallback UI for user preferences.

When a user visits the page, they instantly see this prerendered shell with the header and blog posts. Only the personalized preferences need to stream in at request time since they depend on the user's cookies. This ensures fast initial page loads while still providing personalized content.

Enabling Cache Components

You can enable Cache Components (which includes PPR) by adding thecacheComponents option to your Next config file:

next.config.ts
importtype { NextConfig }from'next'constnextConfig:NextConfig= {  cacheComponents:true,}exportdefault nextConfig

Good to know: When Cache Components is enabled,GET Route Handlers follow the same prerendering model as pages. SeeRoute Handlers with Cache Components for details.

Navigation uses Activity

When thecacheComponents flag is enabled, Next.js uses React's<Activity> component to preserve component state during client-side navigation.

Rather than unmounting the previous route when you navigate away, Next.js sets the Activity mode to"hidden". This means:

  • Component state is preserved when navigating between routes
  • When you navigate back, the previous route reappears with its state intact
  • Effects are cleaned up when a route is hidden, and recreated when it becomes visible again

This behavior improves the navigation experience by maintaining UI state (form inputs, or expanded sections) when users navigate back and forth between routes.

Good to know: Next.js uses heuristics to keep a few recently visited routes"hidden", while older routes are removed from the DOM to prevent excessive growth.

Migrating route segment configs

When Cache Components is enabled, several route segment config options are no longer needed or supported:

dynamic = "force-dynamic"

Not needed. All pages are dynamic by default.

app/page.tsx
// Before - No longer neededexportconstdynamic='force-dynamic'exportdefaultfunctionPage() {return <div>...</div>}
app/page.tsx
// After - Just remove itexportdefaultfunctionPage() {return <div>...</div>}

dynamic = "force-static"

Start by removing it. When unhandled dynamic or runtime data access is detected during development and built time, Next.js raises an error. Otherwise, theprerendering step automatically extracts the static HTML shell.

For dynamic data access, adduse cache as close to the data access as possible with a longcacheLife like'max' to maintain cached behavior. If needed, add it at the top of the page or layout.

For runtime data access (cookies(),headers(), etc.), errors will direct you towrap it withSuspense. Since you started by usingforce-static, you must remove the runtime data access to prevent any request time work.

app/page.tsx
// Beforeexportconstdynamic='force-static'exportdefaultasyncfunctionPage() {constdata=awaitfetch('https://api.example.com/data')return <div>...</div>}
app/page.tsx
import { cacheLife }from'next/cache'// After - Use 'use cache' insteadexportdefaultasyncfunctionPage() {'use cache'cacheLife('max')constdata=awaitfetch('https://api.example.com/data')return <div>...</div>}

revalidate

Replace withcacheLife. Use thecacheLife function to define cache duration instead of the route segment config.

// Beforeexportconstrevalidate=3600// 1 hourexportdefaultasyncfunctionPage() {return <div>...</div>}
app/page.tsx
// After - Use cacheLifeimport { cacheLife }from'next/cache'exportdefaultasyncfunctionPage() {'use cache'cacheLife('hours')return <div>...</div>}

fetchCache

Not needed. Withuse cache, all data fetching within a cached scope is automatically cached, makingfetchCache unnecessary.

app/page.tsx
// BeforeexportconstfetchCache='force-cache'
app/page.tsx
// After - Use 'use cache' to control caching behaviorexportdefaultasyncfunctionPage() {'use cache'// All fetches here are cachedreturn <div>...</div>}

runtime = 'edge'

Not supported. Cache Components requires Node.js runtime and will throw errors withEdge Runtime.

Next Steps

Learn more about the config option for Cache Components.

Was this helpful?

supported.

[8]ページ先頭

©2009-2025 Movatter.jp