Skip to content

Make SSR action-result seeding (#472) work on Bun via a Bun-native facade #529

Description

@vivek7405

Problem

SSR action-result seeding (#472) does not work on Bun. It rides Node's synchronous module.registerHooks load hook, which installs the transparent 'use server' facade that captures each action result during an SSR render and serializes it into the page; the generated RPC stub then reads that seed on its first client call, eliminating the on-hydration re-fetch. Bun has no module.registerHooks, so registerSeedHooks() no-ops there (fail-open, logged once).

Consequence: on Bun, every SHIPPING async-render component (one that also carries a signal / @event, so it is not elided) re-issues its action RPC once on hydration, a round trip Node elides via the seed. Correctness is unaffected (the first paint already has the SSR data, stale-while-revalidate, the re-fetch returns the same value), but it is a real per-component network cost and a Node/Bun parity gap. Display-only async-render components are elided, so they are unaffected.

Design / approach

The #472 facade is deliberately build-free (no source transform): it wraps 'use server' exports in a Proxy at module load via module.registerHooks. To reach parity on Bun without a build step, host the same facade through a Bun-native seam:

  • Bun.plugin loader (preferred): a Bun loader plugin can intercept the .server.{ts,js} module load and return the same re-exporting facade the Node hook produces, feeding the ambient AsyncLocalStorage seed collector. Keep action-seed.js runtime-neutral and select the install mechanism by serverRuntime() (the listener-core seam), the same way the listener shells are chosen.
  • Alternatives to weigh: a Bun --preload registration, or a server-side wrap at the action-registration boundary (where the RPC index is built) instead of at module load, which might be runtime-neutral and could simplify BOTH shells.
  • Must stay consume-once, fail-open, keyed by action-hash + fn + serialized args, identical wire to the Node path, so a page rendered on either runtime seeds the same way.

Distinct from #528 (the correctness bug where a signal-driven async-render component goes inert after the on-hydration re-fetch); this issue is the perf/parity gap that erases the re-fetch on Bun in the first place.

Acceptance criteria

  • A shipping async-render component does NOT re-issue its action RPC on hydration when served on Bun (seed consumed), matching Node.
  • action-seed.js selects the install mechanism by runtime; the Node module.registerHooks path is unchanged.
  • The Bun matrix covers the seeded path (the seed-hook tests currently denylisted for Bun run, or a Bun-native equivalent does).
  • The e2e seeding tests (currently skipped on Bun via SEED_SKIP) run on Bun.
  • Docs updated (the async-render seeding notes in AGENTS.md + agent-docs that say seeding is node-only).

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions