Skip to content

Fix access fallback status for empty PPR shells - #99218

Open
Libes6 wants to merge 2 commits into
vercel:canaryfrom
Libes6:fix-empty-shell-access-fallback-status
Open

Libes6 wants to merge 2 commits into
vercel:canaryfrom
Libes6:fix-empty-shell-access-fallback-status

Conversation

@Libes6

@Libes6 Libes6 commented Sep 25, 2026

Copy link
Copy Markdown

What?

  • Fixes notFound() returning HTTP 200 when it is discovered while resuming an empty PPR shell.
  • Adds production and deploy-compatible end-to-end regression tests.
  • Preserves the existing redirect behavior.

Why?

An empty prerendered shell could be sent before the resumed render completed. This committed the response headers with a 200 status even though the RSC payload correctly contained a 404 access fallback.

The approach in #95561 propagated every status from the resumed render, which caused the existing redirect tests to fail.

How?

For an empty shell, the runtime now waits for the resumed render metadata before sending the response headers.

Only HTTP access fallback statuses (401, 403, and 404) are applied to the document response. For all other cases, including redirects, the original status and Location header are preserved.

The tests cover the first and repeated 404 responses, a valid 200 response, redirect behavior, Webpack, Turbopack, experimental Turbopack, and a deploy-compatible end-to-end scenario.

Related to #98518
Related to #95561

@github-actions

github-actions Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Failing test suites

Commit: 93eeba4 | About building and testing Next.js

pnpm test-dev-experimental-turbo test/development/app-dir/instant-insights-tab-overlay/instant-insights-tab-overlay.test.ts (turbopack) (Experimental) (job)

  • instant insights tab overlay > should wrap the mobile overlay header only when it does not fit (DD)
Expand output

● instant insights tab overlay › should wrap the mobile overlay header only when it does not fit

expect(received).toBeLessThan(expected)

Expected: < 4
Received:   24

  171 |       const layout = await getErrorOverlayNavLayout(browser)
  172 |       expect(layout).not.toBeNull()
> 173 |       expect(Math.abs(layout!.rightTop - layout!.leftTop)).toBeLessThan(4)
      |                                                            ^
  174 |       expect(
  175 |         Math.abs(
  176 |           layout!.navRightEdge - layout!.rightEdge - layout!.navPaddingRight

  at toBeLessThan (development/app-dir/instant-insights-tab-overlay/instant-insights-tab-overlay.test.ts:173:60)
  at retry (lib/next-test-utils.ts:940:14)
  at Object.<anonymous> (development/app-dir/instant-insights-tab-overlay/instant-insights-tab-overlay.test.ts:170:5)

pnpm test-dev test/development/app-dir/instant-insights-tab-overlay/instant-insights-tab-overlay.test.ts (job)

  • instant insights tab overlay > should wrap the mobile overlay header only when it does not fit (DD)
Expand output

● instant insights tab overlay › should wrap the mobile overlay header only when it does not fit

expect(received).toBeLessThan(expected)

Expected: < 4
Received:   24

  171 |       const layout = await getErrorOverlayNavLayout(browser)
  172 |       expect(layout).not.toBeNull()
> 173 |       expect(Math.abs(layout!.rightTop - layout!.leftTop)).toBeLessThan(4)
      |                                                            ^
  174 |       expect(
  175 |         Math.abs(
  176 |           layout!.navRightEdge - layout!.rightEdge - layout!.navPaddingRight

  at toBeLessThan (development/app-dir/instant-insights-tab-overlay/instant-insights-tab-overlay.test.ts:173:60)
  at retry (lib/next-test-utils.ts:940:14)
  at Object.<anonymous> (development/app-dir/instant-insights-tab-overlay/instant-insights-tab-overlay.test.ts:170:5)

pnpm test-deploy test/e2e/app-dir/cache-components/cache-components.test.ts (job)

  • cache-components > should not have route specific errors (DD)
  • cache-components > should not warn about potential memory leak for even listeners on AbortSignal (DD)
  • cache-components > should prerender fully static pages (DD)
  • cache-components > should prerender static not-found pages (DD)
  • cache-components > should render not-found with Suspense in layout without connection errors (DD)
  • cache-components > should apply a not-found status before resuming an empty shell (DD)
  • cache-components > should prerender pages that render in a microtask (DD)
  • cache-components > should partially prerender pages that take longer than a task to render (DD)
  • cache-components > should prerender pages that only use cached fetches (DD)
  • cache-components > should partially prerender pages that use at least one fetch without cache (DD)
  • cache-components > should prerender pages that only use cached (unstable_cache) IO (DD)
  • cache-components > should prerender pages that only use cached ("use cache") IO (DD)
  • cache-components > should prerender pages that cached the whole page (DD)
  • cache-components > should partially prerender pages that do any uncached IO (DD)
  • cache-components > should partially prerender pages that do any uncached IO (use cache) (DD)
  • cache-components > should partially prerender pages that use cookies() (DD)
  • cache-components > should partially prerender pages that use headers() (DD)
  • cache-components > should fully prerender pages that use unstable_noStore() (DD)
  • cache-components > should partially prerender pages that use searchParams in Server Components (DD)
  • cache-components > should partially prerender pages that use searchParams in Client Components (DD)
  • cache-components > can prerender pages with parallel routes that are static (DD)
  • cache-components > can prerender pages with parallel routes that resolve in a microtask (DD)
  • cache-components > does not prerender pages with parallel routes that resolve in a task (DD)
  • cache-components > does not prerender pages with parallel routes that uses a dynamic API (DD)
  • cache-components > should not resume when client components are dynamic but the RSC render was static (DD)
Expand output

● cache-components › should not have route specific errors

Failed to link project  Vercel CLI 62.2.0 (Node.js 20.9.0)
> NOTE: The Vercel CLI now collects telemetry regarding usage of the CLI.
> This information is used to shape the CLI roadmap and prioritize features.
> You can learn more, including how to opt-out if you'd not like to participate in this program, by visiting the following URL:
> https://vercel.com/docs/cli/about-telemetry
Error: No existing credentials found. Please run `vercel login` or pass "--token"
Learn More: https://err.sh/vercel/no-credentials-found (1)

  455 |
  456 |     if (linkRes.exitCode !== 0) {
> 457 |       throw new Error(
      |             ^
  458 |         `Failed to link project ${linkRes.stdout} ${linkRes.stderr} (${linkRes.exitCode})`
  459 |       )
  460 |     }

  at NextDeployInstance.deploy (lib/next-modes/next-deploy.ts:457:13)
  at NextDeployInstance.start (lib/next-modes/next-deploy.ts:1046:5)
  at lib/e2e-utils/index.ts:327:13
  at Span.traceAsyncFn (../packages/next/src/trace/trace.ts:156:14)
  at lib/e2e-utils/index.ts:324:9
  at Span.traceAsyncFn (../packages/next/src/trace/trace.ts:156:14)
  at createNext (lib/e2e-utils/index.ts:279:12)
  at Object.<anonymous> (lib/e2e-utils/index.ts:391:18)

● cache-components › should not warn about potential memory leak for even listeners on AbortSignal

Failed to link project  Vercel CLI 62.2.0 (Node.js 20.9.0)
> NOTE: The Vercel CLI now collects telemetry regarding usage of the CLI.
> This information is used to shape the CLI roadmap and prioritize features.
> You can learn more, including how to opt-out if you'd not like to participate in this program, by visiting the following URL:
> https://vercel.com/docs/cli/about-telemetry
Error: No existing credentials found. Please run `vercel login` or pass "--token"
Learn More: https://err.sh/vercel/no-credentials-found (1)

  455 |
  456 |     if (linkRes.exitCode !== 0) {
> 457 |       throw new Error(
      |             ^
  458 |         `Failed to link project ${linkRes.stdout} ${linkRes.stderr} (${linkRes.exitCode})`
  459 |       )
  460 |     }

  at NextDeployInstance.deploy (lib/next-modes/next-deploy.ts:457:13)
  at NextDeployInstance.start (lib/next-modes/next-deploy.ts:1046:5)
  at lib/e2e-utils/index.ts:327:13
  at Span.traceAsyncFn (../packages/next/src/trace/trace.ts:156:14)
  at lib/e2e-utils/index.ts:324:9
  at Span.traceAsyncFn (../packages/next/src/trace/trace.ts:156:14)
  at createNext (lib/e2e-utils/index.ts:279:12)
  at Object.<anonymous> (lib/e2e-utils/index.ts:391:18)

● cache-components › should prerender fully static pages

Failed to link project  Vercel CLI 62.2.0 (Node.js 20.9.0)
> NOTE: The Vercel CLI now collects telemetry regarding usage of the CLI.
> This information is used to shape the CLI roadmap and prioritize features.
> You can learn more, including how to opt-out if you'd not like to participate in this program, by visiting the following URL:
> https://vercel.com/docs/cli/about-telemetry
Error: No existing credentials found. Please run `vercel login` or pass "--token"
Learn More: https://err.sh/vercel/no-credentials-found (1)

  455 |
  456 |     if (linkRes.exitCode !== 0) {
> 457 |       throw new Error(
      |             ^
  458 |         `Failed to link project ${linkRes.stdout} ${linkRes.stderr} (${linkRes.exitCode})`
  459 |       )
  460 |     }

  at NextDeployInstance.deploy (lib/next-modes/next-deploy.ts:457:13)
  at NextDeployInstance.start (lib/next-modes/next-deploy.ts:1046:5)
  at lib/e2e-utils/index.ts:327:13
  at Span.traceAsyncFn (../packages/next/src/trace/trace.ts:156:14)
  at lib/e2e-utils/index.ts:324:9
  at Span.traceAsyncFn (../packages/next/src/trace/trace.ts:156:14)
  at createNext (lib/e2e-utils/index.ts:279:12)
  at Object.<anonymous> (lib/e2e-utils/index.ts:391:18)

● cache-components › should prerender static not-found pages

Failed to link project  Vercel CLI 62.2.0 (Node.js 20.9.0)
> NOTE: The Vercel CLI now collects telemetry regarding usage of the CLI.
> This information is used to shape the CLI roadmap and prioritize features.
> You can learn more, including how to opt-out if you'd not like to participate in this program, by visiting the following URL:
> https://vercel.com/docs/cli/about-telemetry
Error: No existing credentials found. Please run `vercel login` or pass "--token"
Learn More: https://err.sh/vercel/no-credentials-found (1)

  455 |
  456 |     if (linkRes.exitCode !== 0) {
> 457 |       throw new Error(
      |             ^
  458 |         `Failed to link project ${linkRes.stdout} ${linkRes.stderr} (${linkRes.exitCode})`
  459 |       )
  460 |     }

  at NextDeployInstance.deploy (lib/next-modes/next-deploy.ts:457:13)
  at NextDeployInstance.start (lib/next-modes/next-deploy.ts:1046:5)
  at lib/e2e-utils/index.ts:327:13
  at Span.traceAsyncFn (../packages/next/src/trace/trace.ts:156:14)
  at lib/e2e-utils/index.ts:324:9
  at Span.traceAsyncFn (../packages/next/src/trace/trace.ts:156:14)
  at createNext (lib/e2e-utils/index.ts:279:12)
  at Object.<anonymous> (lib/e2e-utils/index.ts:391:18)

● cache-components › should render not-found with Suspense in layout without connection errors

Failed to link project  Vercel CLI 62.2.0 (Node.js 20.9.0)
> NOTE: The Vercel CLI now collects telemetry regarding usage of the CLI.
> This information is used to shape the CLI roadmap and prioritize features.
> You can learn more, including how to opt-out if you'd not like to participate in this program, by visiting the following URL:
> https://vercel.com/docs/cli/about-telemetry
Error: No existing credentials found. Please run `vercel login` or pass "--token"
Learn More: https://err.sh/vercel/no-credentials-found (1)

  455 |
  456 |     if (linkRes.exitCode !== 0) {
> 457 |       throw new Error(
      |             ^
  458 |         `Failed to link project ${linkRes.stdout} ${linkRes.stderr} (${linkRes.exitCode})`
  459 |       )
  460 |     }

  at NextDeployInstance.deploy (lib/next-modes/next-deploy.ts:457:13)
  at NextDeployInstance.start (lib/next-modes/next-deploy.ts:1046:5)
  at lib/e2e-utils/index.ts:327:13
  at Span.traceAsyncFn (../packages/next/src/trace/trace.ts:156:14)
  at lib/e2e-utils/index.ts:324:9
  at Span.traceAsyncFn (../packages/next/src/trace/trace.ts:156:14)
  at createNext (lib/e2e-utils/index.ts:279:12)
  at Object.<anonymous> (lib/e2e-utils/index.ts:391:18)

● cache-components › should apply a not-found status before resuming an empty shell

Failed to link project  Vercel CLI 62.2.0 (Node.js 20.9.0)
> NOTE: The Vercel CLI now collects telemetry regarding usage of the CLI.
> This information is used to shape the CLI roadmap and prioritize features.
> You can learn more, including how to opt-out if you'd not like to participate in this program, by visiting the following URL:
> https://vercel.com/docs/cli/about-telemetry
Error: No existing credentials found. Please run `vercel login` or pass "--token"
Learn More: https://err.sh/vercel/no-credentials-found (1)

  455 |
  456 |     if (linkRes.exitCode !== 0) {
> 457 |       throw new Error(
      |             ^
  458 |         `Failed to link project ${linkRes.stdout} ${linkRes.stderr} (${linkRes.exitCode})`
  459 |       )
  460 |     }

  at NextDeployInstance.deploy (lib/next-modes/next-deploy.ts:457:13)
  at NextDeployInstance.start (lib/next-modes/next-deploy.ts:1046:5)
  at lib/e2e-utils/index.ts:327:13
  at Span.traceAsyncFn (../packages/next/src/trace/trace.ts:156:14)
  at lib/e2e-utils/index.ts:324:9
  at Span.traceAsyncFn (../packages/next/src/trace/trace.ts:156:14)
  at createNext (lib/e2e-utils/index.ts:279:12)
  at Object.<anonymous> (lib/e2e-utils/index.ts:391:18)

● cache-components › should prerender pages that render in a microtask

Failed to link project  Vercel CLI 62.2.0 (Node.js 20.9.0)
> NOTE: The Vercel CLI now collects telemetry regarding usage of the CLI.
> This information is used to shape the CLI roadmap and prioritize features.
> You can learn more, including how to opt-out if you'd not like to participate in this program, by visiting the following URL:
> https://vercel.com/docs/cli/about-telemetry
Error: No existing credentials found. Please run `vercel login` or pass "--token"
Learn More: https://err.sh/vercel/no-credentials-found (1)

  455 |
  456 |     if (linkRes.exitCode !== 0) {
> 457 |       throw new Error(
      |             ^
  458 |         `Failed to link project ${linkRes.stdout} ${linkRes.stderr} (${linkRes.exitCode})`
  459 |       )
  460 |     }

  at NextDeployInstance.deploy (lib/next-modes/next-deploy.ts:457:13)
  at NextDeployInstance.start (lib/next-modes/next-deploy.ts:1046:5)
  at lib/e2e-utils/index.ts:327:13
  at Span.traceAsyncFn (../packages/next/src/trace/trace.ts:156:14)
  at lib/e2e-utils/index.ts:324:9
  at Span.traceAsyncFn (../packages/next/src/trace/trace.ts:156:14)
  at createNext (lib/e2e-utils/index.ts:279:12)
  at Object.<anonymous> (lib/e2e-utils/index.ts:391:18)

● cache-components › should partially prerender pages that take longer than a task to render

Failed to link project  Vercel CLI 62.2.0 (Node.js 20.9.0)
> NOTE: The Vercel CLI now collects telemetry regarding usage of the CLI.
> This information is used to shape the CLI roadmap and prioritize features.
> You can learn more, including how to opt-out if you'd not like to participate in this program, by visiting the following URL:
> https://vercel.com/docs/cli/about-telemetry
Error: No existing credentials found. Please run `vercel login` or pass "--token"
Learn More: https://err.sh/vercel/no-credentials-found (1)

  455 |
  456 |     if (linkRes.exitCode !== 0) {
> 457 |       throw new Error(
      |             ^
  458 |         `Failed to link project ${linkRes.stdout} ${linkRes.stderr} (${linkRes.exitCode})`
  459 |       )
  460 |     }

  at NextDeployInstance.deploy (lib/next-modes/next-deploy.ts:457:13)
  at NextDeployInstance.start (lib/next-modes/next-deploy.ts:1046:5)
  at lib/e2e-utils/index.ts:327:13
  at Span.traceAsyncFn (../packages/next/src/trace/trace.ts:156:14)
  at lib/e2e-utils/index.ts:324:9
  at Span.traceAsyncFn (../packages/next/src/trace/trace.ts:156:14)
  at createNext (lib/e2e-utils/index.ts:279:12)
  at Object.<anonymous> (lib/e2e-utils/index.ts:391:18)

● cache-components › should prerender pages that only use cached fetches

Failed to link project  Vercel CLI 62.2.0 (Node.js 20.9.0)
> NOTE: The Vercel CLI now collects telemetry regarding usage of the CLI.
> This information is used to shape the CLI roadmap and prioritize features.
> You can learn more, including how to opt-out if you'd not like to participate in this program, by visiting the following URL:
> https://vercel.com/docs/cli/about-telemetry
Error: No existing credentials found. Please run `vercel login` or pass "--token"
Learn More: https://err.sh/vercel/no-credentials-found (1)

  455 |
  456 |     if (linkRes.exitCode !== 0) {
> 457 |       throw new Error(
      |             ^
  458 |         `Failed to link project ${linkRes.stdout} ${linkRes.stderr} (${linkRes.exitCode})`
  459 |       )
  460 |     }

  at NextDeployInstance.deploy (lib/next-modes/next-deploy.ts:457:13)
  at NextDeployInsta

... truncated ...

pnpm test-dev-turbo test/development/app-dir/instant-insights-tab-overlay/instant-insights-tab-overlay.test.ts (turbopack) (job)

  • instant insights tab overlay > should wrap the mobile overlay header only when it does not fit (DD)
Expand output

● instant insights tab overlay › should wrap the mobile overlay header only when it does not fit

expect(received).toBeLessThan(expected)

Expected: < 4
Received:   24

  171 |       const layout = await getErrorOverlayNavLayout(browser)
  172 |       expect(layout).not.toBeNull()
> 173 |       expect(Math.abs(layout!.rightTop - layout!.leftTop)).toBeLessThan(4)
      |                                                            ^
  174 |       expect(
  175 |         Math.abs(
  176 |           layout!.navRightEdge - layout!.rightEdge - layout!.navPaddingRight

  at toBeLessThan (development/app-dir/instant-insights-tab-overlay/instant-insights-tab-overlay.test.ts:173:60)
  at retry (lib/next-test-utils.ts:940:14)
  at Object.<anonymous> (development/app-dir/instant-insights-tab-overlay/instant-insights-tab-overlay.test.ts:170:5)

Libes6 commented Oct 3, 2026

Copy link
Copy Markdown
Author

The failing deploy test appears to be caused by missing Vercel credentials in the fork PR environment (No existing credentials found), rather than by the assertions in this change.

The other reported failures are from instant-insights-tab-overlay and appear unrelated to this PR.

The regular build and build-and-deploy checks are passing. Let me know if you'd like me to rerun or adjust anything on my side.

Libes6 commented Oct 7, 2026

Copy link
Copy Markdown
Author

Small update: #99436 enables Cache Components by default for new apps in 16.4.

Since this issue happens in the PPR resume path, I think it’s a bit more important now than when I originally opened the PR.

@Libes6

Libes6 commented Oct 14, 2026

Copy link
Copy Markdown
Author

Hi Next.js team! I'd appreciate a maintainer review or some direction on this fix.

This issue is affecting our production application. With cacheComponents enabled, calling notFound() for a non-existent dynamic route can still result in HTTP 200. This creates soft-404 responses and breaks the expected HTTP contract for our pages.

I've included regression tests covering 404 responses, valid 200 responses, and redirect behavior.

I also noticed that Cache Components are now enabled by default in newly created applications (#99436), which makes this issue potentially relevant to more projects.

Some CI failures appear unrelated to the changes, including deployment tests failing because Vercel credentials are unavailable. I'm happy to investigate any failures caused by this PR.

Could a maintainer take a look and confirm whether the approach of waiting for resumed render metadata on empty PPR shells is acceptable?

If there's a better approach, I'd be happy to adapt the implementation.

Thanks!

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant