Skip to content

PPR producer B: emit the shell entry at BUILD time for prerendered routes (Prerender + ppr = zero runtime capture) #699

Closed

Description

@ivogt

What

For a route combining a build-time handler with the shell option —

path("/docs/:slug", Prerender(getParams, handler), { name: "docs", ppr: { ttl, swr } })

— produce the complete PPR shell entry at vite build, so the route serves x-rango-shell: HIT from the very first request. Today the composition (verified end-to-end in #697) is: build-time Flight payload + runtime-captured HTML prelude — the first visitor triggers a background capture that replays the build-time segments (no handler execution) and freezes the prelude into the shell store. Producer B removes that last runtime step.

Why

  • First-request HIT. No cold MISS, no capture latency, no per-deploy warm-up traffic. A static marketing page is a build-time entry with a long TTL whose live holes (loaders) still stream per request.
  • Determinism. The runtime capture path is physics-sensitive (quiet windows, cold module graphs, background-task I/O semantics) — the entire CI hardening campaign in fix(vite): prerender collection ran trieless — catch-all shadowed nested routes; Prerender + ppr composition coverage #697 (capture serialization, queue caps, store-fetch timeouts) exists because of it. Build-time production has none of those variables: no ambient identity (the identity guard is trivially satisfied), no quiet-window race, no waitUntil.
  • One model. docs/design/shell-fast-path.md pins this as the designed end state: pre-rendering is caching at build time becomes literal — one entry format, two producers (runtime capture / build), one consumer (the shell serve path). The worker cannot tell where an entry came from.

Mechanism sketch

The entry format already carries everything (ShellCacheEntry: prelude, postponed, reactVersion, buildVersion, initialTheme, snapshot incl. the implicit doc segment record from #695/#697, handlerLiveHoles):

  1. During expandPrerenderRoutes (build), for ppr-flagged prerender routes, additionally run the capture pipeline under BuildContext: Flight-render the resolved segments (masked loaders → holes exactly as at runtime), fizz prerender + abort, collect prelude + postponed.
  2. Persist the entry with the build artifacts; at boot (or first getShell), seed the shell store from the build manifest — or serve build entries through a read-through layer the same way the prerender payload manifest works today (no static file serving; the worker handles every request, per the pre-rendering hard rule).
  3. TTL semantics: build entries default to no-expiry-until-next-deploy (buildVersion gate already evicts on deploy); ppr.ttl + runtime recapture can still refresh on top (SWR recapture becomes the upgrade path from build entry to fresher runtime entry, not the bootstrap path).
  4. Eligibility: handlerLiveHoles detection runs identically during the build capture (handler-invoked loaders / nested-thenable handle pushes decline the fast path, same as runtime).

Acceptance

  • Prerender + ppr route: first-ever request after deploy is x-rango-shell: HIT with the frozen prelude + streaming loader holes (dev + production e2e, test-app + cloudflare-basic).
  • Loaders stay live per request; updateTag/buildVersion evict build entries like runtime ones.
  • No static .html/.rsc served from assets (worker-handles-every-request rule upheld).
  • The runtime capture path remains for non-prerendered ppr routes, unchanged.

References

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions