Skip to content

Exclude static metadata and viewport from error recovery prerender when cache-components is enabled#94134

Open
gnoff wants to merge 1 commit into
canaryfrom
jstory/limit-http-error-recovery-metadata
Open

Exclude static metadata and viewport from error recovery prerender when cache-components is enabled#94134
gnoff wants to merge 1 commit into
canaryfrom
jstory/limit-http-error-recovery-metadata

Conversation

@gnoff
Copy link
Copy Markdown
Contributor

@gnoff gnoff commented May 26, 2026

These functions can be dynamic however in this mode we are trying to recover on the client so there is little point in attempting to resolve the http error recovery metadata since it might block us producing a shell. We now exclude these from the errore recovery RSC payload since we will recover the metadata on hydration in the browser.

@gnoff gnoff requested a review from unstubbable May 26, 2026 21:11
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 26, 2026

Failing test suites

Commit: d058ed1 | About building and testing Next.js

pnpm test-dev test/e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts (job)

  • parallel-routes-and-interception > should not render the @children slot when the @slot is not found (DD)
Expand output

● parallel-routes-and-interception › should not render the @children slot when the @slot is not found

expect(received).toBe(expected) // Object.is equality

Expected: 0
Received: 1

  23 |     expect(await browser.hasElementByCssSelector('#children-slot')).toBe(false)
  24 |     const $ = await next.render$('/')
> 25 |     expect($('title').length).toBe(0)
     |                               ^
  26 |   })
  27 |
  28 |   it('should render the title once for the non-existed route', async () => {

  at Object.toBe (e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts:25:31)

pnpm test-dev test/e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts (job)

  • parallel-routes-and-interception > should not render the @children slot when the @slot is not found (DD)
Expand output

● parallel-routes-and-interception › should not render the @children slot when the @slot is not found

expect(received).toBe(expected) // Object.is equality

Expected: 0
Received: 1

  23 |     expect(await browser.hasElementByCssSelector('#children-slot')).toBe(false)
  24 |     const $ = await next.render$('/')
> 25 |     expect($('title').length).toBe(0)
     |                               ^
  26 |   })
  27 |
  28 |   it('should render the title once for the non-existed route', async () => {

  at Object.toBe (e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts:25:31)

pnpm test-dev test/e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts (job)

  • parallel-routes-and-interception > should not render the @children slot when the @slot is not found (DD)
Expand output

● parallel-routes-and-interception › should not render the @children slot when the @slot is not found

expect(received).toBe(expected) // Object.is equality

Expected: 0
Received: 1

  23 |     expect(await browser.hasElementByCssSelector('#children-slot')).toBe(false)
  24 |     const $ = await next.render$('/')
> 25 |     expect($('title').length).toBe(0)
     |                               ^
  26 |   })
  27 |
  28 |   it('should render the title once for the non-existed route', async () => {

  at Object.toBe (e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts:25:31)

pnpm test-start test/e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts (job)

  • parallel-routes-and-interception > should not render the @children slot when the @slot is not found (DD)
Expand output

● parallel-routes-and-interception › should not render the @children slot when the @slot is not found

expect(received).toBe(expected) // Object.is equality

Expected: 0
Received: 1

  23 |     expect(await browser.hasElementByCssSelector('#children-slot')).toBe(false)
  24 |     const $ = await next.render$('/')
> 25 |     expect($('title').length).toBe(0)
     |                               ^
  26 |   })
  27 |
  28 |   it('should render the title once for the non-existed route', async () => {

  at Object.toBe (e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts:25:31)

pnpm test-dev test/e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts (job)

  • parallel-routes-and-interception > should not render the @children slot when the @slot is not found (DD)
Expand output

● parallel-routes-and-interception › should not render the @children slot when the @slot is not found

expect(received).toBe(expected) // Object.is equality

Expected: 0
Received: 1

  23 |     expect(await browser.hasElementByCssSelector('#children-slot')).toBe(false)
  24 |     const $ = await next.render$('/')
> 25 |     expect($('title').length).toBe(0)
     |                               ^
  26 |   })
  27 |
  28 |   it('should render the title once for the non-existed route', async () => {

  at Object.toBe (e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts:25:31)

pnpm test-dev test/e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts (job)

  • parallel-routes-and-interception > should not render the @children slot when the @slot is not found (DD)
Expand output

● parallel-routes-and-interception › should not render the @children slot when the @slot is not found

expect(received).toBe(expected) // Object.is equality

Expected: 0
Received: 1

  23 |     expect(await browser.hasElementByCssSelector('#children-slot')).toBe(false)
  24 |     const $ = await next.render$('/')
> 25 |     expect($('title').length).toBe(0)
     |                               ^
  26 |   })
  27 |
  28 |   it('should render the title once for the non-existed route', async () => {

  at Object.toBe (e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts:25:31)

pnpm test-dev test/development/app-dir/instant-navs-devtools/instant-navs-devtools.test.ts (job)

  • instant-nav-panel > transitions between capture types > should avoid showing stale SPA state when reloading from a captured SPA state into captured MPA state (DD)
Expand output

● instant-nav-panel › transitions between capture types › should avoid showing stale SPA state when reloading from a captured SPA state into captured MPA state

locator.waitFor: Timeout 30000ms exceeded.
Call log:
  - waiting for locator('[data-testid="dynamic-skeleton"]') to be visible

  206 |     await browser
  207 |       .locator('[data-testid="dynamic-skeleton"]')
> 208 |       .waitFor({ state: 'visible', timeout: 30000 })
      |        ^
  209 |     await browser
  210 |       .locator('[data-testid="param-skeleton"]')
  211 |       .waitFor({ state: 'visible' })

  at waitFor (development/app-dir/instant-navs-devtools/instant-navs-devtools.test.ts:208:8)
  at Object.expectTargetPageMpaShell (development/app-dir/instant-navs-devtools/instant-navs-devtools.test.ts:579:13)

pnpm test-start test/e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts (job)

  • parallel-routes-and-interception > should not render the @children slot when the @slot is not found (DD)
Expand output

● parallel-routes-and-interception › should not render the @children slot when the @slot is not found

expect(received).toBe(expected) // Object.is equality

Expected: 0
Received: 1

  23 |     expect(await browser.hasElementByCssSelector('#children-slot')).toBe(false)
  24 |     const $ = await next.render$('/')
> 25 |     expect($('title').length).toBe(0)
     |                               ^
  26 |   })
  27 |
  28 |   it('should render the title once for the non-existed route', async () => {

  at Object.toBe (e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts:25:31)

pnpm test-start test/e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts (job)

  • parallel-routes-and-interception > should not render the @children slot when the @slot is not found (DD)
Expand output

● parallel-routes-and-interception › should not render the @children slot when the @slot is not found

expect(received).toBe(expected) // Object.is equality

Expected: 0
Received: 1

  23 |     expect(await browser.hasElementByCssSelector('#children-slot')).toBe(false)
  24 |     const $ = await next.render$('/')
> 25 |     expect($('title').length).toBe(0)
     |                               ^
  26 |   })
  27 |
  28 |   it('should render the title once for the non-existed route', async () => {

  at Object.toBe (e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts:25:31)

pnpm test-dev-turbo test/e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts (turbopack) (job)

  • parallel-routes-and-interception > should not render the @children slot when the @slot is not found (DD)
Expand output

● parallel-routes-and-interception › should not render the @children slot when the @slot is not found

expect(received).toBe(expected) // Object.is equality

Expected: 0
Received: 1

  23 |     expect(await browser.hasElementByCssSelector('#children-slot')).toBe(false)
  24 |     const $ = await next.render$('/')
> 25 |     expect($('title').length).toBe(0)
     |                               ^
  26 |   })
  27 |
  28 |   it('should render the title once for the non-existed route', async () => {

  at Object.toBe (e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts:25:31)

pnpm test-start-turbo test/e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts (turbopack) (job)

  • parallel-routes-and-interception > should not render the @children slot when the @slot is not found (DD)
Expand output

● parallel-routes-and-interception › should not render the @children slot when the @slot is not found

expect(received).toBe(expected) // Object.is equality

Expected: 0
Received: 1

  23 |     expect(await browser.hasElementByCssSelector('#children-slot')).toBe(false)
  24 |     const $ = await next.render$('/')
> 25 |     expect($('title').length).toBe(0)
     |                               ^
  26 |   })
  27 |
  28 |   it('should render the title once for the non-existed route', async () => {

  at Object.toBe (e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts:25:31)

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 26, 2026

Stats cancelled

Commit: d058ed1
View workflow run

Copy link
Copy Markdown
Contributor

@unstubbable unstubbable left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the title assertion in test/e2e/app-dir/parallel-routes-not-found/parallel-routes-not-found.test.ts needs to be adjusted.

Comment thread packages/next/src/server/app-render/app-render.tsx Outdated
@gnoff gnoff force-pushed the jstory/limit-http-error-recovery-metadata branch from c34f326 to bfa493b Compare May 27, 2026 16:16
…en cache-components is enabled

These functions can be dynamic however in this mode we are trying to recover on the client so there is little point in attempting to resolve the http error recovery metadata since it might block us producing a shell. We now exclude these from the errore recovery RSC payload since we will recover the metadata on hydration in the browser.
@gnoff gnoff force-pushed the jstory/limit-http-error-recovery-metadata branch from bfa493b to d058ed1 Compare May 27, 2026 16:19
const $ = await next.render$('/')
expect($('title').length).toBe(1)
expect($('title').text()).toBe('layout title')
expect($('title').length).toBe(0)
Copy link
Copy Markdown
Contributor

@vercel vercel Bot May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test assertion expect($('title').length).toBe(0) was changed unconditionally, but the <title> tag is only absent in cache-components mode — regular prod mode still renders it, causing the test to fail outside cache-components CI.

Fix on Vercel

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gnoff I though you'd do the same check as here. That should work for both with and without cacheComponents, right?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point — using a browser.eval() + retry() approach like the new cache-components test would work in both modes since the title will be present client-side regardless (either from SSR or from hydration recovery). Either approach fixes the issue; I'll defer to @gnoff on which feels more appropriate here.

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.

3 participants