Skip to content

Extract App Shell from static prefetches#94095

Draft
acdlite wants to merge 2 commits into
acdlite/app-shells/03-clientfrom
acdlite/app-shells/05-client-rewind
Draft

Extract App Shell from static prefetches#94095
acdlite wants to merge 2 commits into
acdlite/app-shells/03-clientfrom
acdlite/app-shells/05-client-rewind

Conversation

@acdlite
Copy link
Copy Markdown
Contributor

@acdlite acdlite commented May 25, 2026

Adds support for extracting an App Shell from a more concrete prerender response. The server sends down a byte offset that represents the subset of the stream that corresponds to the reusable App Shell.

This does not yet implement shell extraction for per-segment prefetch responses. Implementing this adds an additional layer of complexity, because those responses are generated during a separate phase of the build process. We do intend to implement this, but it's a non-essential optimization that can come later.

This also does not yet implement shell extraction from a navigation response (the Cached Navigations feature), though both features are based on essentially the same mechanism. I'm deferring this to a subsequent PR because some of the existing implementation needs to be rethought in light of the new App Shells based model; for example, the "static stage" boundary might not make sense to track separately from the App Shell.

The main practical upshot of the PR is that if you have a fully statically prerendered page with no dynamic holes, that page's App Shell can now be fetched by the client without incurring any runtime server execution cost: the server will return the full static page, and the client will extract the App Shell from that concrete response.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 25, 2026

Failing test suites

Commit: b48293d | About building and testing Next.js

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

  • Cache Components Errors > Build With --prerender-debug > Dynamic Metadata - Error Route > should error the build for the correct reason when there is a cache components violation alongside dynamic metadata (DD)
  • Cache Components Errors > Build With --prerender-debug > Dynamic Root > should error the build if cache components happens in the root (outside a Suspense) (DD)
  • Cache Components Errors > Build With --prerender-debug > Error Attribution with Sync IO > Unguarded RSC with guarded Client sync IO > should error the build with a reason related dynamic data (DD)
Expand output

● Cache Components Errors › Build With --prerender-debug › Dynamic Metadata - Error Route › should error the build for the correct reason when there is a cache components violation alongside dynamic metadata

expect(received).toMatchInlineSnapshot(snapshot)

Snapshot name: `Cache Components Errors Build With --prerender-debug Dynamic Metadata - Error Route should error the build for the correct reason when there is a cache components violation alongside dynamic metadata 1`

- Snapshot  - 9
+ Received  + 8

@@ -7,19 +7,18 @@
    - Provide a placeholder with `<Suspense fallback={...}>` around the data access
    - If the runtime data is `params` and they're known, prerender them with `generateStaticParams`
    - Set `export const instant = false` to allow a blocking route

  Learn more: https://nextjs.org/docs/messages/blocking-route
-     at Dynamic (webpack:///app/dynamic-metadata-error-route/page.tsx:20:16)
-     at Page (webpack:///app/dynamic-metadata-error-route/page.tsx:15:7)
+     at Page (webpack:///app/dynamic-metadata-error-route/page.tsx:15:7)
+   13 |         incorrectly report this as an invariant error.
-   18 | }
-   19 |
+   14 |       </p>
- > 20 | async function Dynamic() {
+ > 15 |       <Dynamic />
-      |                ^
+      |       ^
-   21 |   await new Promise((r) => setTimeout(r))
+   16 |     </>
-   22 |   return <p id="dynamic">Dynamic</p>
+   17 |   )
-   23 | }
+   18 | }
  To debug the issue, start the app in development mode by running `next dev`, then open "/dynamic-metadata-error-route" in your browser to investigate the error.
  Error occurred prerendering page "/dynamic-metadata-error-route". Read more: https://nextjs.org/docs/messages/prerender-error

  > Export encountered errors on 1 path:
  	/dynamic-metadata-error-route/page: /dynamic-metadata-error-route"

  241 |           } else {
  242 |             if (isDebugPrerender) {
> 243 |               expect(output).toMatchInlineSnapshot(`
      |                              ^
  244 |                "Error: Route "/dynamic-metadata-error-route": Next.js encountered uncached or runtime data during prerendering.
  245 |
  246 |                \`fetch(...)\`, \`cookies()\`, \`headers()\`, \`params\`, \`searchParams\`, or \`connection()\` accessed outside of \`<Suspense>\` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.

  at Object.toMatchInlineSnapshot (e2e/app-dir/cache-components-errors/cache-components-errors.test.ts:243:30)

● Cache Components Errors › Build With --prerender-debug › Dynamic Root › should error the build if cache components happens in the root (outside a Suspense)

expect(received).toMatchInlineSnapshot(snapshot)

Snapshot name: `Cache Components Errors Build With --prerender-debug Dynamic Root should error the build if cache components happens in the root (outside a Suspense) 1`

- Snapshot  - 20
+ Received  + 16

@@ -7,20 +7,18 @@
    - Provide a placeholder with `<Suspense fallback={...}>` around the data access
    - If the runtime data is `params` and they're known, prerender them with `generateStaticParams`
    - Set `export const instant = false` to allow a blocking route

  Learn more: https://nextjs.org/docs/messages/blocking-route
-     at fetchRandom (webpack:///app/dynamic-root/page.tsx:62:16)
-     at FetchingComponent (webpack:///app/dynamic-root/page.tsx:46:56)
-     at Page (webpack:///app/dynamic-root/page.tsx:23:9)
+     at Page (webpack:///app/dynamic-root/page.tsx:23:9)
+   21 |       </IndirectionOne>
-   60 |   // Hide uncached I/O behind a runtime API call, to ensure we still get the
-   61 |   // correct owner stack for the error.
- > 62 |   await cookies()
+   22 |       <IndirectionTwo>
+ > 23 |         <FetchingComponent nonce="c" />
-      |                ^
-   63 |   const response = await fetch(
+      |         ^
+   24 |         <Suspense fallback="loading...">
-   64 |     'https://next-data-api-endpoint.vercel.app/api/random?b=' + entropy
-   65 |   )
+   25 |           <FetchingComponent nonce="d" />
+   26 |         </Suspense>
  To debug the issue, start the app in development mode by running `next dev`, then open "/dynamic-root" in your browser to investigate the error.
  Error: Route "/dynamic-root": Next.js encountered uncached or runtime data during prerendering.

  `fetch(...)`, `cookies()`, `headers()`, `params`, `searchParams`, or `connection()` accessed outside of `<Suspense>` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.

@@ -29,20 +27,18 @@
    - Provide a placeholder with `<Suspense fallback={...}>` around the data access
    - If the runtime data is `params` and they're known, prerender them with `generateStaticParams`
    - Set `export const instant = false` to allow a blocking route

  Learn more: https://nextjs.org/docs/messages/blocking-route
-     at fetchRandom (webpack:///app/dynamic-root/page.tsx:62:16)
-     at FetchingComponent (webpack:///app/dynamic-root/page.tsx:46:56)
-     at Page (webpack:///app/dynamic-root/page.tsx:28:7)
+     at Page (webpack:///app/dynamic-root/page.tsx:28:7)
+   26 |         </Suspense>
-   60 |   // Hide uncached I/O behind a runtime API call, to ensure we still get the
-   61 |   // correct owner stack for the error.
- > 62 |   await cookies()
+   27 |       </IndirectionTwo>
+ > 28 |       <FetchingComponent nonce="e" />
-      |                ^
+      |       ^
-   63 |   const response = await fetch(
+   29 |       <Suspense fallback="loading...">
-   64 |     'https://next-data-api-endpoint.vercel.app/api/random?b=' + entropy
-   65 |   )
+   30 |         <FetchingComponent nonce="f" />
+   31 |       </Suspense>
  To debug the issue, start the app in development mode by running `next dev`, then open "/dynamic-root" in your browser to investigate the error.
  Error occurred prerendering page "/dynamic-root". Read more: https://nextjs.org/docs/messages/prerender-error

  > Export encountered errors on 1 path:
  	/dynamic-root/page: /dynamic-root"

  914 |           } else {
  915 |             if (isDebugPrerender) {
> 916 |               expect(output).toMatchInlineSnapshot(`
      |                              ^
  917 |                "Error: Route "/dynamic-root": Next.js encountered uncached or runtime data during prerendering.
  918 |
  919 |                \`fetch(...)\`, \`cookies()\`, \`headers()\`, \`params\`, \`searchParams\`, or \`connection()\` accessed outside of \`<Suspense>\` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.

  at Object.toMatchInlineSnapshot (e2e/app-dir/cache-components-errors/cache-components-errors.test.ts:916:30)

● Cache Components Errors › Build With --prerender-debug › Error Attribution with Sync IO › Unguarded RSC with guarded Client sync IO › should error the build with a reason related dynamic data

expect(received).toMatchInlineSnapshot(snapshot)

Snapshot name: `Cache Components Errors Build With --prerender-debug Error Attribution with Sync IO Unguarded RSC with guarded Client sync IO should error the build with a reason related dynamic data 1`

- Snapshot  - 9
+ Received  + 8

@@ -7,19 +7,18 @@
    - Provide a placeholder with `<Suspense fallback={...}>` around the data access
    - If the runtime data is `params` and they're known, prerender them with `generateStaticParams`
    - Set `export const instant = false` to allow a blocking route

  Learn more: https://nextjs.org/docs/messages/blocking-route
-     at RequestData (webpack:///app/sync-attribution/unguarded-async-guarded-clientsync/page.tsx:34:18)
-     at Page (webpack:///app/sync-attribution/unguarded-async-guarded-clientsync/page.tsx:27:9)
+     at Page (webpack:///app/sync-attribution/unguarded-async-guarded-clientsync/page.tsx:27:9)
+   25 |       </section>
-   32 |
-   33 | async function RequestData() {
- > 34 |   ;(await cookies()).get('foo')
-      |                  ^
-   35 |   return (
+   26 |       <section>
+ > 27 |         <RequestData />
+      |         ^
+   28 |       </section>
-   36 |     <div>
+   29 |     </main>
-   37 |       <h2>Request Data Access</h2>
+   30 |   )
  To debug the issue, start the app in development mode by running `next dev`, then open "/sync-attribution/unguarded-async-guarded-clientsync" in your browser to investigate the error.
  Error occurred prerendering page "/sync-attribution/unguarded-async-guarded-clientsync". Read more: https://nextjs.org/docs/messages/prerender-error

  > Export encountered errors on 1 path:
  	/sync-attribution/unguarded-async-guarded-clientsync/page: /sync-attribution/unguarded-async-guarded-clientsync"

  2409 |             } else {
  2410 |               if (isDebugPrerender) {
> 2411 |                 expect(output).toMatchInlineSnapshot(`
       |                                ^
  2412 |                  "Error: Route "/sync-attribution/unguarded-async-guarded-clientsync": Next.js encountered uncached or runtime data during prerendering.
  2413 |
  2414 |                  \`fetch(...)\`, \`cookies()\`, \`headers()\`, \`params\`, \`searchParams\`, or \`connection()\` accessed outside of \`<Suspense>\` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.

  at Object.toMatchInlineSnapshot (e2e/app-dir/cache-components-errors/cache-components-errors.test.ts:2411:32)

pnpm test-start-turbo test/e2e/app-dir/cache-components-errors/cache-components-errors.test.ts (turbopack) (job)

  • Cache Components Errors > Build With --prerender-debug > Dynamic Metadata - Error Route > should error the build for the correct reason when there is a cache components violation alongside dynamic metadata (DD)
  • Cache Components Errors > Build With --prerender-debug > Dynamic Root > should error the build if cache components happens in the root (outside a Suspense) (DD)
  • Cache Components Errors > Build With --prerender-debug > Error Attribution with Sync IO > Unguarded RSC with guarded Client sync IO > should error the build with a reason related dynamic data (DD)
  • Cache Components Errors > Build With --prerender-debug > Inside use cache > cacheLife with expire < 5 minutes > microtasky cache > should error the build (DD)
  • Cache Components Errors > Build With --prerender-debug > Inside use cache > cacheLife with expire < 5 minutes > slow cache > should error the build (DD)
  • Cache Components Errors > Build With --prerender-debug > Inside use cache > cacheLife with revalidate: 0 > microtasky cache > should error the build (DD)
  • Cache Components Errors > Build With --prerender-debug > Inside use cache > cacheLife with revalidate: 0 > slow cache > should error the build (DD)
  • Cache Components Errors > Build With --prerender-debug > Inside use cache > reading fallback params > should error the build (DD)
  • Cache Components Errors > Build With --prerender-debug > With use cache: private > without Suspense > should error the build (DD)
Expand output

● Cache Components Errors › Build With --prerender-debug › Dynamic Metadata - Error Route › should error the build for the correct reason when there is a cache components violation alongside dynamic metadata

expect(received).toMatchInlineSnapshot(snapshot)

Snapshot name: `Cache Components Errors Build With --prerender-debug Dynamic Metadata - Error Route should error the build for the correct reason when there is a cache components violation alongside dynamic metadata 1`

- Snapshot  - 9
+ Received  + 8

@@ -7,19 +7,18 @@
    - Provide a placeholder with `<Suspense fallback={...}>` around the data access
    - If the runtime data is `params` and they're known, prerender them with `generateStaticParams`
    - Set `export const instant = false` to allow a blocking route

  Learn more: https://nextjs.org/docs/messages/blocking-route
-     at Dynamic (app/dynamic-metadata-error-route/page.tsx:20:16)
-     at Page (app/dynamic-metadata-error-route/page.tsx:15:7)
+     at Page (app/dynamic-metadata-error-route/page.tsx:15:7)
+   13 |         incorrectly report this as an invariant error.
-   18 | }
-   19 |
- > 20 | async function Dynamic() {
+   14 |       </p>
+ > 15 |       <Dynamic />
-      |                ^
+      |       ^
-   21 |   await new Promise((r) => setTimeout(r))
+   16 |     </>
-   22 |   return <p id="dynamic">Dynamic</p>
+   17 |   )
-   23 | }
+   18 | }
  To debug the issue, start the app in development mode by running `next dev`, then open "/dynamic-metadata-error-route" in your browser to investigate the error.
  Error occurred prerendering page "/dynamic-metadata-error-route". Read more: https://nextjs.org/docs/messages/prerender-error

  > Export encountered errors on 1 path:
  	/dynamic-metadata-error-route/page: /dynamic-metadata-error-route"

  189 |           if (isTurbopack) {
  190 |             if (isDebugPrerender) {
> 191 |               expect(output).toMatchInlineSnapshot(`
      |                              ^
  192 |                "Error: Route "/dynamic-metadata-error-route": Next.js encountered uncached or runtime data during prerendering.
  193 |
  194 |                \`fetch(...)\`, \`cookies()\`, \`headers()\`, \`params\`, \`searchParams\`, or \`connection()\` accessed outside of \`<Suspense>\` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.

  at Object.toMatchInlineSnapshot (e2e/app-dir/cache-components-errors/cache-components-errors.test.ts:191:30)

● Cache Components Errors › Build With --prerender-debug › Dynamic Root › should error the build if cache components happens in the root (outside a Suspense)

expect(received).toMatchInlineSnapshot(snapshot)

Snapshot name: `Cache Components Errors Build With --prerender-debug Dynamic Root should error the build if cache components happens in the root (outside a Suspense) 1`

- Snapshot  - 20
+ Received  + 16

@@ -7,20 +7,18 @@
    - Provide a placeholder with `<Suspense fallback={...}>` around the data access
    - If the runtime data is `params` and they're known, prerender them with `generateStaticParams`
    - Set `export const instant = false` to allow a blocking route

  Learn more: https://nextjs.org/docs/messages/blocking-route
-     at fetchRandom (app/dynamic-root/page.tsx:62:16)
-     at FetchingComponent (app/dynamic-root/page.tsx:46:56)
-     at Page (app/dynamic-root/page.tsx:23:9)
+     at Page (app/dynamic-root/page.tsx:23:9)
+   21 |       </IndirectionOne>
-   60 |   // Hide uncached I/O behind a runtime API call, to ensure we still get the
-   61 |   // correct owner stack for the error.
- > 62 |   await cookies()
-      |                ^
-   63 |   const response = await fetch(
+   22 |       <IndirectionTwo>
+ > 23 |         <FetchingComponent nonce="c" />
+      |         ^
+   24 |         <Suspense fallback="loading...">
-   64 |     'https://next-data-api-endpoint.vercel.app/api/random?b=' + entropy
-   65 |   )
+   25 |           <FetchingComponent nonce="d" />
+   26 |         </Suspense>
  To debug the issue, start the app in development mode by running `next dev`, then open "/dynamic-root" in your browser to investigate the error.
  Error: Route "/dynamic-root": Next.js encountered uncached or runtime data during prerendering.

  `fetch(...)`, `cookies()`, `headers()`, `params`, `searchParams`, or `connection()` accessed outside of `<Suspense>` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.

@@ -29,20 +27,18 @@
    - Provide a placeholder with `<Suspense fallback={...}>` around the data access
    - If the runtime data is `params` and they're known, prerender them with `generateStaticParams`
    - Set `export const instant = false` to allow a blocking route

  Learn more: https://nextjs.org/docs/messages/blocking-route
-     at fetchRandom (app/dynamic-root/page.tsx:62:16)
-     at FetchingComponent (app/dynamic-root/page.tsx:46:56)
-     at Page (app/dynamic-root/page.tsx:28:7)
+     at Page (app/dynamic-root/page.tsx:28:7)
+   26 |         </Suspense>
-   60 |   // Hide uncached I/O behind a runtime API call, to ensure we still get the
-   61 |   // correct owner stack for the error.
- > 62 |   await cookies()
-      |                ^
-   63 |   const response = await fetch(
+   27 |       </IndirectionTwo>
+ > 28 |       <FetchingComponent nonce="e" />
+      |       ^
+   29 |       <Suspense fallback="loading...">
-   64 |     'https://next-data-api-endpoint.vercel.app/api/random?b=' + entropy
-   65 |   )
+   30 |         <FetchingComponent nonce="f" />
+   31 |       </Suspense>
  To debug the issue, start the app in development mode by running `next dev`, then open "/dynamic-root" in your browser to investigate the error.
  Error occurred prerendering page "/dynamic-root". Read more: https://nextjs.org/docs/messages/prerender-error

  > Export encountered errors on 1 path:
  	/dynamic-root/page: /dynamic-root"

  814 |           if (isTurbopack) {
  815 |             if (isDebugPrerender) {
> 816 |               expect(output).toMatchInlineSnapshot(`
      |                              ^
  817 |                "Error: Route "/dynamic-root": Next.js encountered uncached or runtime data during prerendering.
  818 |
  819 |                \`fetch(...)\`, \`cookies()\`, \`headers()\`, \`params\`, \`searchParams\`, or \`connection()\` accessed outside of \`<Suspense>\` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.

  at Object.toMatchInlineSnapshot (e2e/app-dir/cache-components-errors/cache-components-errors.test.ts:816:30)

● Cache Components Errors › Build With --prerender-debug › Error Attribution with Sync IO › Unguarded RSC with guarded Client sync IO › should error the build with a reason related dynamic data

expect(received).toMatchInlineSnapshot(snapshot)

Snapshot name: `Cache Components Errors Build With --prerender-debug Error Attribution with Sync IO Unguarded RSC with guarded Client sync IO should error the build with a reason related dynamic data 1`

- Snapshot  - 9
+ Received  + 8

@@ -7,19 +7,18 @@
    - Provide a placeholder with `<Suspense fallback={...}>` around the data access
    - If the runtime data is `params` and they're known, prerender them with `generateStaticParams`
    - Set `export const instant = false` to allow a blocking route

  Learn more: https://nextjs.org/docs/messages/blocking-route
-     at RequestData (app/sync-attribution/unguarded-async-guarded-clientsync/page.tsx:34:18)
-     at Page (app/sync-attribution/unguarded-async-guarded-clientsync/page.tsx:27:9)
+     at Page (app/sync-attribution/unguarded-async-guarded-clientsync/page.tsx:27:9)
+   25 |       </section>
-   32 |
-   33 | async function RequestData() {
- > 34 |   ;(await cookies()).get('foo')
-      |                  ^
-   35 |   return (
+   26 |       <section>
+ > 27 |         <RequestData />
+      |         ^
+   28 |       </section>
-   36 |     <div>
+   29 |     </main>
-   37 |       <h2>Request Data Access</h2>
+   30 |   )
  To debug the issue, start the app in development mode by running `next dev`, then open "/sync-attribution/unguarded-async-guarded-clientsync" in your browser to investigate the error.
  Error occurred prerendering page "/sync-attribution/unguarded-async-guarded-clientsync". Read more: https://nextjs.org/docs/messages/prerender-error

  > Export encountered errors on 1 path:
  	/sync-attribution/unguarded-async-guarded-clientsync/page: /sync-attribution/unguarded-async-guarded-clientsync"

  2355 |             if (isTurbopack) {
  2356 |               if (isDebugPrerender) {
> 2357 |                 expect(output).toMatchInlineSnapshot(`
       |                                ^
  2358 |                  "Error: Route "/sync-attribution/unguarded-async-guarded-clientsync": Next.js encountered uncached or runtime data during prerendering.
  2359 |
  2360 |                  \`fetch(...)\`, \`cookies()\`, \`headers()\`, \`params\`, \`searchParams\`, or \`connection()\` accessed outside of \`<Suspense>\` prevents the route from being prerendered, blocking the page load and leading to a slower user experience.

  at Object.toMatchInlineSnapshot (e2e/app-dir/cache-components-errors/cache-components-errors.test.ts:2357:32)

● Cache Components Errors › Build With --prerender-debug › Inside use cache › cacheLife with expire < 5 minutes › microtasky cache › should error the build

expect(received).toMatchInlineSnapshot(snapshot)

Snapshot name: `Cache Components Errors Build With --prerender-debug Inside \`use cache\` cacheLife with expire < 5 minutes microtasky cache should error the build 1`

- Snapshot  - 8
+ Received  + 1

@@ -7,18 +7,11 @@
    - Provide a placeholder with `<Suspense fallback={...}>` around the data access
    - If the runtime data is `params` and they're known, prerender them with `generateStaticParams`
    - Set `export const instant = false` to allow a blocking route

  Learn more: https://nextjs.org/docs/messages/blocking-route
-     at Page (app/use-cache-low-expire/fast/page.tsx:3:16)
-   1 | import { cacheLife } from 'next/cache'
-   2 |
- > 3 | export default async function Page() {
-     |                ^
-   4 |   'use cache: remote'
-   5 |
-   6 |   cacheLife({ expire: 299 }) // 1 second below the threshold of 5 minutes
+     at Page [Server] (<anonymous>)
  To debug the issue, start the app in development mode by running `next dev`, then open "/use-cache-low-expire/fast" in your browser to investigate the error.
  Error occurred prerendering page "/use-cache-low-expire/fast". Read more: https://nextjs.org/docs/messages/prerender-error

  > Export encountered errors on 1 path:
  	/use-cache-low-expire/fast/page: /use-cache-low-expire/fast"

  3070 |               if (isTurbopack) {
  3071 |                 if (isDebugPrerender) {
> 3072 |                   expect(output).toMatchInlineSnapshot(`
       |                                  ^
  3073 |                    "Error: Route "/use-cache-low-expire/fast": Next.js encountered uncached or runtime data during prerendering.
  3074 |
  3075 |                    \`fetch(...)\`, \`cookies()\`, \`headers()\`, \`params\`, \`searchParams\`, or \`connection()\` accessed outside of \`<Sus

... truncated ...

acdlite and others added 2 commits May 25, 2026 04:40
Adds support for extracting an App Shell from a more concrete
prerender response. The server sends down a byte offset that
represents the subset of the stream that corresponds to the
reusable App Shell.

This does _not_ yet implement shell extraction for per-segment
prefetch responses. Implementing this adds an additional layer of
complexity, because those responses are generated during a separate
phase of the build process. We do intend to implement this, but
it's a non-essential optimization that can come later.

This also does not yet implement shell extraction from a navigation
response (the Cached Navigations feature), though both features are
based on essentially the same mechanism. I'm deferring this to a
subsequent PR because some of the existing implementation needs to
be rethought in light of the new App Shells based model; for
example, the "static stage" boundary might not make sense to track
separately from the App Shell.

The main practical upshot of the PR is that if you have a fully
statically prerendered page with no dynamic holes, that page's App
Shell can now be fetched by the client without incurring any
runtime server execution cost: the server will return the full
static page, and the client will extract the App Shell from that
concrete response.
Squashed server-only diff from:
  - #94044  Streaming prerender
  - #93801  Rewinding app shells

This commit is a placeholder on the stack so we can develop and test the
client-side rewinding integration that lands in the commit above. It gets
regenerated from Janka's latest each time we re-sync; do not edit by hand.

For now this uses only the fallback-stage branch as source because its
app-render.tsx version supersedes streaming-prerender's (the smaller
streaming-prerender files are byte-identical to fallback-stage's).
Replaces the forceOmitParams hanging-promise mechanism from canary with
the abort-before-params approach Janka builds on.
@acdlite acdlite force-pushed the acdlite/app-shells/05-client-rewind branch from 8fbc171 to b48293d Compare May 25, 2026 08:43
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.

2 participants