next.js icon indicating copy to clipboard operation
next.js copied to clipboard

Cache components does not work on dynamic segments when localized

Open maxscn opened this issue 1 month ago • 5 comments

Link to the code that reproduces this issue

https://github.com/maxscn/intlayer-cache-components

To Reproduce

  1. Start the dev server.
  2. Go to /en/static (cached) and then /en/static/1 (not cached)
  3. Go to /static (cached) and then /static/1 (cached)

Current vs. Expected behavior

Following the steps, I expected the localized content to work exactly as the non-localized content. The locale is known at build time hence it should be possible to cache even the localized pages.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.1.0: Mon Oct 20 19:34:03 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T8112
  Available memory (MB): 16384
  Available CPU cores: 8
Binaries:
  Node: 22.16.0
  npm: 10.9.2
  Yarn: N/A
  pnpm: N/A
Relevant Packages:
  next: 16.0.7 // Latest available version is detected (16.0.7).
  eslint-config-next: N/A
  react: 19.2.0
  react-dom: 19.2.0
  typescript: 5.9.3
Next.js Config:
  output: N/A

Which area(s) are affected? (Select all that apply)

cacheComponents, internationalization (i18n)

Which stage(s) are affected? (Select all that apply)

next build (local), next dev (local), Vercel (Deployed)

Additional context

Every big localization framework which works with react server components (I have tried intlayer, next-intl and lingui), breaks when using it with cache components. The reason that they break seems to be that we are accessing params, which forces the route to be dynamic. Even if I cache the locale param at the layout level, when I access my params at page level I opt out since that is an asynchronous call. The result is that if you translate any content at all (in server components), your entire page becomes dynamic. All of the big localization framework demands that you do this to translate server side components.

maxscn avatar Dec 05 '25 14:12 maxscn

Found that if you put generateStaticParams at each dynamic segment it seems to work as I want it to. It feels like this is unintended however. If it is intended it would be a very nice thing to document.

maxscn avatar Dec 05 '25 15:12 maxscn

A more clear doc update is in review/draft, but I think even that might also miss on the benefits of prerendering when theres' two params too, cuz /every-locale/[id] will also generate a static shell, per locale value.

Anyway, the docs do have some thin info about this, param is runtime data unless generateStaticParams is defined to return at least one param.

I'll get better soon 🙏

icyJoseph avatar Dec 05 '25 15:12 icyJoseph

Hey @icyJoseph, in my case the [locale]/[...not-found] route causes the same error

Removing /src/app/[locale]/(landing)/[...not-found]/page.tsx fixes it

aymericzip avatar Dec 07 '25 16:12 aymericzip

Hey @icyJoseph, in my case the [locale]/[...not-found] route causes the same error

Removing /src/app/[locale]/(landing)/[...not-found]/page.tsx fixes it

What route are you left with when you've removed /src/app/[locale]/(landing)/[...not-found]/page.tsx? Or are you referring to the route group (landing)?

@aymericzip

adamsoderstrom avatar Dec 08 '25 07:12 adamsoderstrom

tree .
.
├── intlayer.config.ts
├── next.config.ts
├── node_modules
├── src
│   ├── app
│   │   ├── [locale]
│   │   │   ├── (landing)
│   │   │   │   ├── [...not-found]
│   │   │   │   │   ├── layout.tsx
│   │   │   │   │   ├── metadata.content.ts
│   │   │   │   │   ├── metadata.ts
│   │   │   │   │   ├── not-fount.content.ts
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── 404
│   │   │   │   │   ├── layout.tsx
│   │   │   │   │   └── page.tsx
│   │   │   │   ├── layout.tsx
│   │   │   │   ├── page.tsx
...
├── tsconfig.json
└── types
    └── markdown.d.ts

The error was showing on every page, and after I removed the [...not-found]/** content, it disappeared

Gemini found the issue :

The error "Uncached data was accessed outside of <Suspense>" occurs because you are using the Next.js 16 Cache Components feature (likely the experimental.ppr or dynamic IO flags), which enforces strict rules about data fetching and dynamic access.
Here is the explanation:
Why [...not-found]?
Your other pages are likely Static because you have generateStaticParams in apps/website/src/app/[locale]/layout.tsx. This means they are pre-rendered at build time, so they don't trigger runtime data access checks.
The [...not-found] route is a catch-all route. Since it cannot statically generate every possible unknown URL, it runs Dynamically (at runtime) for every request that doesn't match a static path.
The "Uncached Data" Issue:
When [...not-found] renders at runtime, it uses PageLayout, which renders <IntlayerClientProvider>.
It appears IntlayerClientProvider (or a component it renders) is performing a data fetch (e.g., loading dictionaries) or accessing dynamic data (headers/cookies) that is not cached.

@adamsoderstrom

aymericzip avatar Dec 08 '25 20:12 aymericzip