Skip to content

Catch-call route /[...slugs] causing dynamic page content to be served from cache #3511

Description

@ndh103

@netlify/plugin-nextjs@4.41.5 ODB routing conflict with catch-all route and causing dynamic route to be served from cache.

Environment

  • Next.js 14 (14.2.35)
  • App Router
  • @netlify/plugin-nextjs version "^4.41.3",
  • Deployed on Netlify

Reproduction

This is a minimal project to reproduce the reported behavior:
https://github.com/dunfe/netlify-next-first-user-cache-repro/tree/main

Netflix url: https://netlify-next-first-user-cache-repro.netlify.app/

Video: https://github.com/dunfe/netlify-next-first-user-cache-repro/blob/main/output.mp4

  • /dashboard is a dynamic App Router page (ƒ).
  • It reads the current user from the user cookie on every SSR render.
  • app/[...slug]/page.tsx is an App Router SSG catch-all route. When Contentful is healthy it renders generated child pages; when simulated Contentful connection fails, it still appears as ● /[...slug] without child paths.
  • A simulated Contentful build failure is enabled with SIMULATE_CONTENTFUL_FAILURE=1.
  • When that flag is enabled, middleware adds a cacheable Cache-Control header to /dashboard.
  • On Netlify, the Next adapter/runtime can promote that response to Netlify-CDN-Cache-Control: ..., durable while the default Netlify-Vary does not vary by auth cookie.
  • Result: the first user to hit /dashboard after deploy can seed the cached dashboard response for later users.
  • This project is pinned to Next.js 14.2.35 and @netlify/plugin-nextjs 4.41.5 to test the older runtime where the issue was reported.

Reproduce with browser

After the deploy is live, test client-side navigation, not only direct hard-refresh document loads.

  • Open a fresh browser profile as user A.
  • Visit / first.
  • Type A in the User name text box.
  • Click Login as user. The form sets document.cookie = 'user=A; Path=/; SameSite=Lax' and performs an App Router router.push('/dashboard') without a hard refresh.
  • Open another browser/profile as user B.
  • Visit / first.
  • Type B in the User name text box.
  • Click Login as user.
  • You can also switch users from the manual login section on /dashboard; it updates the user cookie and refreshes the route without opening browser devtools.

OBSERVED BEHAVIOR:

  • User B request has Cookie: user=B.
  • The browser RSC request for /dashboard?_rsc=... can return user A's RSC payload if A was first.
  • Direct document requests to /dashboard may still look correct because the leak is in the App Router RSC/flight payload.
  • If B is first after deploy, A later sees B.

NOTE:

Expected Behavior

Since /dashboard is a dynamic route, there should be no cache content served.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions