-
Notifications
You must be signed in to change notification settings - Fork 30k
Description
Link to the code that reproduces this issue
https://github.com/ViniciusHack/next-usecache-loading-conflict.git
To Reproduce
On Production (Vercel)
This environment shows the core cache failure on dynamic routes when streaming is active.
- Static Path Check (Cache Baseline):
- Navigate to the root and click "Post 1 (Has
loading.tsx)". (Path:/post/1) - Observation: Navigation is near-instant, no visible loading state.
- Result: This works as expected (static cache hit).
- Navigate to the root and click "Post 1 (Has
- Dynamic Path - Streaming/Bug Scenario:
- Go back home and click "Post 2 (Has
loading.tsx)". (Path:/post/2) - Observation (Initial Visit): A visible loading state is shown, then the content loads.
- Go back home and click "Post 2 (Has
- Dynamic Path - Streaming/Bug Confirmation:
- Refresh the page (F5) while on
/post/2. - Observation: The loading state is visible again, and the component re-executes as the page delays 5 seconds to return
- Result: Cache failure. The on-demand ISR cache entry was not saved during the initial streamed response.
- Refresh the page (F5) while on
- Dynamic Path - Blocking/Working Scenario:
- Go back home and click "Post 2 (No
loading.tsx)". (Path:/post-no-loading/2) - Observation (Initial Visit): The browser freezes/blocks completely for 5 seconds (the time of the data fetch + delay).
- Go back home and click "Post 2 (No
- Dynamic Path - Blocking/Working Confirmation:
- Refresh the page (F5) while on
/post-no-loading/2. - Observation: The content appears instantly, with no visible delay, loading state, or re-execution (cache hit).
- Result: Cache success.
- Refresh the page (F5) while on
On Local next dev
This environment shows the development environment cannot correctly simulate the streaming/cache conflict.
- Navigate to any path (
/post/1,/post/2, etc.). - Observation: The
loading.tsxis not triggered, it always produce a blocking route. Although, it caches.
On Local next start (Production Build)
This environment shows the production build often prioritizes the streaming shell.
- Build the project (
next build) and run it locally (next start). - Navigate to a dynamic path that has
loading.tsx(e.g.,/post/2). - Observation: The
loading.tsxfallback is always shown, even after the cache, for a few miliseconds after the first visit.
Current vs. Expected behavior
Expected behavior:
- First visit → shows loading when not statically generated, streams content, caches the result
- Subsequent visits → serves cached content directly (no loading shown)
Current behavior:
- First visit → shows loading when not statically generated, streams content
- Subsequent visits → shows loading again, re-executes page (ignores cache)
Provide environment information
Operating System:
Platform: darwin
Arch: arm64
Version: Darwin Kernel Version 25.1.0: Mon Oct 20 19:32:56 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T8132
Available memory (MB): 16384
Available CPU cores: 10
Binaries:
Node: 22.15.0
npm: 10.9.2
Yarn: N/A
pnpm: 9.11.0
Relevant Packages:
next: 16.0.10 // Latest available version is detected (16.0.10).
eslint-config-next: N/A
react: 19.2.1
react-dom: 19.2.1
typescript: 5.9.3
Next.js Config:
output: N/AWhich area(s) are affected? (Select all that apply)
Partial Prerendering (PPR), Dynamic Routes, cacheComponents, Linking and Navigating, Loading UI and Streaming, Performance, React, Runtime, Use Cache
Which stage(s) are affected? (Select all that apply)
Vercel (Deployed), next start (local), next dev (local)
Additional context
The following is what happens in Vercel logs:
For `/post/1`, we have a Cache Key of `/posts/1` (HIT), and no loading is shown, even though the page has `loading.tsx`. This is expected behavior for a static path.
For `/post/2`, the Cache Key is always `/posts/[id]`, with the status varying between **PRERENDER** and **HIT**. There is no **ISR Function Invocation**, yet the data function is invoked (Function Invocation is present).
For `/post-no-loading/2`, the first request triggers an **ISR Function Invocation (Dynamic Rendering)**, which blocks the route and **UPDATES the cache** (even without `use cache: remote`). Subsequent requests then use the specific Cache Key `/posts-no-loading/[id]` with the dynamic parameter `nxtPid 2`.
It looks like when there is no static shell for /posts/[id], it successfully generates and writes the cache entry using the specific nxtPid. Conversely, when a static shell is present, it does not write the cache with the nxtPid. To summarize, the presence of a pre-existing Static Shell is what prevents the new on-demand cache (the dynamic variant) from being written.