Skip to content

[cacheComponents] stale redirect marker in hidden Activity route causes later Links to full-page navigate #97989

Description

@maxesse

Link to the code that reproduces this issue

https://github.com/maxesse/next-redirect-marker-repro

To Reproduce

  1. Clone the reproduction and run it with next build && next start.
  2. Visit the redirect-capable route. It renders a component behind Suspense which calls redirect(), causing Next to add #__next-page-redirect to the streamed document.
  3. Navigate away using <Link>. With Cache Components enabled, the previous route is retained in a hidden React Activity.
  4. Confirm that document.getElementById('__next-page-redirect') still finds the marker in the hidden route.
  5. Click another unrelated same-origin <Link>.

The final click performs a full document navigation instead of a normal App Router navigation.

The reproduction includes a small window sentinel and network checks to make the document navigation easy to see. Without the stale marker, the same Link click is a normal SPA navigation.

I reproduced this on Next 16.3.3 and 16.4.0-canary.8.

Current vs. Expected behavior

Current behavior

A redirect marker belonging to an inactive route retained in a hidden Activity affects later navigation from the active route.

The cause appears to be the document-wide check in navigate-reducer:

if (document.getElementById('__next-page-redirect')) {
  return completeHardNavigation(state, url, navigateType)
}

Because the lookup searches the whole document, it also finds a redirect marker inside an inactive Activity subtree and turns an unrelated same-origin Link click into a full-page navigation.

I originally found this while using @next/playwright's instant() helper. In that case the unexpected MPA navigation lands on the PPR shell, which makes the failure initially look like cached content was not prefetched.

Expected behavior

A redirect marker belonging to an inactive hidden route should not affect navigation from the currently active route.

Once a streamed redirect is no longer relevant, its marker should either be removed/consumed, or the navigation check should only consider a redirect belonging to the active route.

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.6.0: Fri Jul 31 19:17:26 PDT 2026; root:xnu-12377.161.14~5/RELEASE_ARM64_T6041
  Available memory (MB): 36864
  Available CPU cores: 14
Binaries:
  Node: 24.16.0
  npm: 12.0.2
  Yarn: 1.22.22
  pnpm: 11.21.0
Relevant Packages:
  next: 16.4.0-canary.9
  eslint-config-next: N/A
  react: 19.2.8
  react-dom: 19.2.8
  typescript: 5.9.3
Next.js Config:
  output: N/A

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

cacheComponents, Linking and Navigating, Redirects, Loading UI and Streaming

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

next build (local), next start (local)

Additional context

This looks related to #92767 and the Next team regression test in #92615, but I believe it is a separate case.

Those concern stale RedirectBoundary/route state being restored from Activity. This reproduction is specifically about the streamed __next-page-redirect DOM marker remaining in an inactive route and later being found by the document-global check in navigate-reducer.

It is also adjacent to the broader hidden Activity behavior tracked in #86577.

The real application where I found this uses a request-time authentication/access check behind Suspense, which may call redirect('/sign-in'). This is why a streamed redirect can occur during an otherwise valid cached navigation, but the minimal reproduction does not use an auth library.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Linking and NavigatingRelated to Next.js linking (e.g., <Link>) and navigation.Loading UI and StreamingRelated to loading UI (loading.tsx) and streaming.RedirectsRelated to redirecting.

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions