nextjs - 💡(How to fix) Fix Cache Components with Page params errors with uncached data message [8 comments, 4 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#85807Fetched 2026-04-05 18:21:07
View on GitHub
Comments
8
Participants
4
Timeline
21
Reactions
0
Timeline (top)
commented ×8subscribed ×5mentioned ×4closed ×1

Error Message

We tried cacheComponents and were, just like you, completely confused. Now with cacheComponents, accessing params throws this error, forcing us to either: Bottom line: we're now forced to opt out of cacheComponents entirely because this error makes absolutely no sense. Accessing params shouldn't hinder us from fetching an external post and using that data to render the externally fetched part statically. That's the whole point. Either the error is wrong, or the docs need an immediate update explaining why this formerly recommended pattern is no longer viable. This issue should definitely be reopened as it is still occurs in the latest 16.0.2 canary build. @icyJoseph

Root Cause

function RecommendationsList({ items }) { return ( <ul> {items.map((item) => ( <li key={item.id}>{item.name}</li> ))} </ul> ) }

> **Don't get fooled @daanvosdewael. Wrapping everything in Suspense or adding a loading.tsx is exactly what you DON'T want to do. That defeats the entire purpose of PPR.**
> 
> Your post is completely justified, and let me explain why this is a critical issue that breaks a fundamental performance optimization pattern.
> 
> We run a site with 2M+ monthly users where performance isn't optional. It's how we dominate the competition and keep server-load low. The pattern we use, shown in the [docs example](https://nextjs.org/docs/app/api-reference/directives/use-cache-remote#mixed-caching-strategies) you mentioned in your post, is **exactly** how we achieve this.
> 
> We tried cacheComponents and were, just like you, completely confused. Now with `cacheComponents`, accessing params throws this error, forcing us to either:
> 1. Wrap everything in Suspense (destroying the static shell that PPR is designed to create)
> 2. Completely opt out of cache components and switch back to version 15 canary to use experimental_ppr (which still works perfectly and as intended!)
> 
> The whole point of PPR is to serve a static shell instantly while streaming dynamic parts. If you wrap your entire page in Suspense, you've just made everything dynamic. You're back to square one with no static shell, no instant paint, and SEO takes a hit.
> 
> This destroys the ability to statically render external fetched data while keeping dynamic elements separate, which is the exact use case your documentation promotes.
> 
> This isn't a minor DX inconvenience. This is a performance regression that impacts real production applications at scale.
> 
> **Bottom line: we're now forced to opt out of cacheComponents entirely because this error makes absolutely no sense.** Accessing params shouldn't hinder us from fetching an external post and using that data to render the externally fetched part statically. That's the whole point.
> 
> Either the error is wrong, or the docs need an immediate update explaining why this formerly recommended pattern is no longer viable. This issue should definitely be reopened as it is still occurs in the latest 16.0.2 canary build. @icyJoseph

Code Example

import { Suspense } from 'react'
import { connection } from 'next/server'
import { cookies } from 'next/headers'
import { cacheLife, cacheTag } from 'next/cache'
 
// Static product data - prerendered at build time
async function getProduct(id: string) {
  'use cache'
  cacheTag(`product-${id}`)
 
  // This is cached at build time and shared across all users
  return db.products.find({ where: { id } })
}
 
// Shared pricing data - cached at runtime in remote handler
async function getProductPrice(id: string) {
  'use cache: remote'
  cacheTag(`product-price-${id}`)
  cacheLife({ expire: 300 }) // 5 minutes
 
  // This is cached at runtime and shared across all users
  return db.products.getPrice({ where: { id } })
}
 
// User-specific recommendations - private cache per user
async function getRecommendations(productId: string) {
  'use cache: private'
  cacheLife({ expire: 60 }) // 1 minute
 
  const sessionId = (await cookies()).get('session-id')?.value
 
  // This is cached per-user and never shared
  return db.recommendations.findMany({
    where: { productId, sessionId },
  })
}
 
export default async function ProductPage({ params }) {
  const { id } = await params
 
  // Static product data
  const product = await getProduct(id)
 
  return (
    <div>
      <ProductDetails product={product} />
 
      {/* Dynamic shared price */}
      <Suspense fallback={<PriceSkeleton />}>
        <ProductPriceComponent productId={id} />
      </Suspense>
 
      {/* Dynamic personalized recommendations */}
      <Suspense fallback={<RecommendationsSkeleton />}>
        <ProductRecommendations productId={id} />
      </Suspense>
    </div>
  )
}
 
function ProductDetails({ product }) {
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </div>
  )
}
 
async function ProductPriceComponent({ productId }) {
  // Defer to request time
  await connection()
 
  const price = await getProductPrice(productId)
  return <div>Price: ${price}</div>
}
 
async function ProductRecommendations({ productId }) {
  const recommendations = await getRecommendations(productId)
  return <RecommendationsList items={recommendations} />
}
 
function PriceSkeleton() {
  return <div>Loading price...</div>
}
 
function RecommendationsSkeleton() {
  return <div>Loading recommendations...</div>
}
 
function RecommendationsList({ items }) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  )
}
RAW_BUFFERClick to expand / collapse

Sorry to bring up a close issue. I just hit the same problem when I try to adopt cacheComponent.

What is the recommended way to handle this situation with await params

The renders are not always depend on params or searchParams. For eg, if the searchParams is only just a ?redirect=url, in this case, the render is totally independent of the searchParmas

The Mixed caching strategies(code cited below) given by the Doc is not working.

import { Suspense } from 'react'
import { connection } from 'next/server'
import { cookies } from 'next/headers'
import { cacheLife, cacheTag } from 'next/cache'
 
// Static product data - prerendered at build time
async function getProduct(id: string) {
  'use cache'
  cacheTag(`product-${id}`)
 
  // This is cached at build time and shared across all users
  return db.products.find({ where: { id } })
}
 
// Shared pricing data - cached at runtime in remote handler
async function getProductPrice(id: string) {
  'use cache: remote'
  cacheTag(`product-price-${id}`)
  cacheLife({ expire: 300 }) // 5 minutes
 
  // This is cached at runtime and shared across all users
  return db.products.getPrice({ where: { id } })
}
 
// User-specific recommendations - private cache per user
async function getRecommendations(productId: string) {
  'use cache: private'
  cacheLife({ expire: 60 }) // 1 minute
 
  const sessionId = (await cookies()).get('session-id')?.value
 
  // This is cached per-user and never shared
  return db.recommendations.findMany({
    where: { productId, sessionId },
  })
}
 
export default async function ProductPage({ params }) {
  const { id } = await params
 
  // Static product data
  const product = await getProduct(id)
 
  return (
    <div>
      <ProductDetails product={product} />
 
      {/* Dynamic shared price */}
      <Suspense fallback={<PriceSkeleton />}>
        <ProductPriceComponent productId={id} />
      </Suspense>
 
      {/* Dynamic personalized recommendations */}
      <Suspense fallback={<RecommendationsSkeleton />}>
        <ProductRecommendations productId={id} />
      </Suspense>
    </div>
  )
}
 
function ProductDetails({ product }) {
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
    </div>
  )
}
 
async function ProductPriceComponent({ productId }) {
  // Defer to request time
  await connection()
 
  const price = await getProductPrice(productId)
  return <div>Price: ${price}</div>
}
 
async function ProductRecommendations({ productId }) {
  const recommendations = await getRecommendations(productId)
  return <RecommendationsList items={recommendations} />
}
 
function PriceSkeleton() {
  return <div>Loading price...</div>
}
 
function RecommendationsSkeleton() {
  return <div>Loading recommendations...</div>
}
 
function RecommendationsList({ items }) {
  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>{item.name}</li>
      ))}
    </ul>
  )
}

Don't get fooled @daanvosdewael. Wrapping everything in Suspense or adding a loading.tsx is exactly what you DON'T want to do. That defeats the entire purpose of PPR.

Your post is completely justified, and let me explain why this is a critical issue that breaks a fundamental performance optimization pattern.

We run a site with 2M+ monthly users where performance isn't optional. It's how we dominate the competition and keep server-load low. The pattern we use, shown in the docs example you mentioned in your post, is exactly how we achieve this.

We tried cacheComponents and were, just like you, completely confused. Now with cacheComponents, accessing params throws this error, forcing us to either:

  1. Wrap everything in Suspense (destroying the static shell that PPR is designed to create)
  2. Completely opt out of cache components and switch back to version 15 canary to use experimental_ppr (which still works perfectly and as intended!)

The whole point of PPR is to serve a static shell instantly while streaming dynamic parts. If you wrap your entire page in Suspense, you've just made everything dynamic. You're back to square one with no static shell, no instant paint, and SEO takes a hit.

This destroys the ability to statically render external fetched data while keeping dynamic elements separate, which is the exact use case your documentation promotes.

This isn't a minor DX inconvenience. This is a performance regression that impacts real production applications at scale.

Bottom line: we're now forced to opt out of cacheComponents entirely because this error makes absolutely no sense. Accessing params shouldn't hinder us from fetching an external post and using that data to render the externally fetched part statically. That's the whole point.

Either the error is wrong, or the docs need an immediate update explaining why this formerly recommended pattern is no longer viable. This issue should definitely be reopened as it is still occurs in the latest 16.0.2 canary build. @icyJoseph

Originally posted by @xyba1337 in #85807

extent analysis

TL;DR

  • The error "Uncached data was accessed outside of <Suspense>" can be resolved by wrapping the component that fetches data in a Suspense boundary.

Guidance

  • Review the Mixed caching strategies example and the Params and SearchParams example to understand how Suspense is used with page parameters and caching.
  • Wrap the UsersPage component in a Suspense boundary to allow for asynchronous data fetching without blocking the page render.
  • Verify that the cacheComponents configuration is correctly set up to work with page parameters and external data fetching.
  • Check the Next.js documentation for any updates or changes to caching and suspense usage.

Example

import { Suspense } from 'react';

export default async function UsersPage({ params }: PageProps<'/users/[id]'>) {
  const { id } = await params;
  const user = await getUser(id);

  return (
    <Suspense fallback={<div>Loading...</div>}>
      <pre>{JSON.stringify(user, null, 2)}</pre>
    </Suspense>
  );
}

Notes

  • The provided code and examples may not be entirely up-to-date with the latest Next.js version, so it's essential to verify the documentation for any changes or updates.
  • The usage of cacheComponents with page parameters and external data fetching may require additional configuration or setup.

Recommendation

  • Apply workaround: Wrap the component in a Suspense boundary to resolve the error and improve page rendering performance.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

nextjs - 💡(How to fix) Fix Cache Components with Page params errors with uncached data message [8 comments, 4 participants]