Skip to content

Dynamic Routes Only Cache When Rendering is Blocking (No Streaming) #87125

@ViniciusHack

Description

@ViniciusHack

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.

  1. 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).
  2. 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.
  3. 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.
  4. 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).
  5. 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.

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.tsx is 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.tsx fallback 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/A

Which 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:

Image 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. Image 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). Image Image 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Dynamic RoutesRelated to dynamic routes.Linking and NavigatingRelated to Next.js linking (e.g., <Link>) and navigation.Loading UI and StreamingRelated to loading UI (loading.tsx) and streaming.PerformanceAnything with regards to Next.js performance.ReactRelated to React.RuntimeRelated to Node.js or Edge Runtime with Next.js.

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions