Cache Components (opt-in via cacheComponents: true) introduce a rendering model called:
Partial Prerendering (PPR)
Next.js prerenders as much as possible at build time into a static HTML shell, then selectively streams or reuses dynamic pieces.
This gives you:
✅ Static-site speed
✅ Dynamic data where needed
✅ Smaller JS bundles
At build time, Next.js walks your component tree and asks:
“Can this complete without a request?”
<Suspense>'use cache'If you don’t handle it explicitly, you’ll see:
Uncached data was accessed outside of <Suspense>
These are safe to execute during prerendering:
import fs from 'node:fs'export default async function Page() {const content = fs.readFileSync('./config.json', 'utf-8')const data = JSON.parse(content)return <div>{data.title}</div>}
➡ This becomes part of the static shell automatically.
If something needs live data (API, DB, etc.), wrap it in Suspense:
<Suspense fallback={<p>Loading...</p>}><DynamicContent /></Suspense>
What happens:
This enables parallel rendering instead of blocking the page.
'use cache'This is the key feature.
import { cacheLife } from 'next/cache'export default async function Page() {'use cache'cacheLife('hours')const users = await db.query('SELECT * FROM users')return (<ul>{users.map((user) => (<li key={user.id}>{user.name}</li>))}</ul>)}
Now:
✔ Runs once.
✔ Included in static shell.
✔ Reused across users.
✔ Revalidated later.
const session = (await cookies()).get('session')?.valuereturn <CachedContent sessionId={session} />
async function CachedContent({ sessionId }) {'use cache'return fetchUserData(sessionId)}
The argument becomes part of the cache key.
cacheTag()Attach labels to cached data.
'use cache'cacheTag('posts')
updateTag() — Immediate RefreshUsed after mutations (e.g., cart updates).
updateTag('cart')
Instantly invalidates and refreshes cache.
revalidateTag() — Background RefreshrevalidateTag('posts', 'max')
Fast response now, fresh data soon.
export default function BlogPage() {return (<><Header /> {/* Static */}<BlogPosts /> {/* Cached */}<Suspense fallback={<p>Loading preferences...</p>}><UserPreferences /> {/* Dynamic */}</Suspense></>)}
Users see:
⚡ Instant layout.
⚡ Cached content ready.
⏳ Personalized data streams in.