react-server675fbba4
react-servercommit196ba2e8acbf

feat: use cache request provider support in ssr layer (#368)

Summary

Add full "use cache: request" provider support across the RSC, SSR, and client (browser) environments, enabling per-request deduplication of expensive computations that works transparently across rendering layers.

Problem

React Server Components and SSR run in separate environments (often separate threads). When a function marked with "use cache: request" is called from both a server component and a client component during the same request, the function body executes multiple times — once per environment. There's no mechanism to share the cached result across the RSC → SSR boundary, and no way to hydrate the value in the browser without prop-drilling.

Solution

This PR introduces a cross-environment shared cache protocol with two modes:

  • Worker thread mode (production): A SharedArrayBuffer-based append-only log with Atomics.wait/Atomics.notify for lock-free cross-thread communication. The RSC thread writes serialized cache entries; the SSR worker thread blocks until entries arrive.
  • In-process mode (dev/edge): A plain Map-backed cache with the same read/write API, used when RSC and SSR run on the same thread. Values are serialized using new synchronous RSC Flight protocol helpers (syncToBuffer / syncFromBuffer) added to @lazarv/rsc, preserving all RSC-supported types (Date, Map, Set, RegExp, URL, typed arrays, React elements, etc.) across the thread boundary.

Key changes

@lazarv/rsc — Synchronous Flight serialization

  • syncToBuffer(value) — Synchronously serializes a value into an RSC Flight payload buffer (server)
  • syncFromBuffer(buffer) — Synchronously deserializes an RSC Flight payload buffer (client/SSR)

@lazarv/react-server/cache — Request cache infrastructure

  • request-cache-shared.mjs (new) — SharedArrayBuffer protocol (createSharedRequestCache, attachSharedRequestCache) and in-process fallback (createInProcessRequestCache)
  • cache/index.mjs — Server-side useCache gains a dedicated request-provider fast path with in-process dedup, thenable annotation for React's use(), and SharedArrayBuffer write-through
  • cache/ssr.mjs (new) — SSR-specific useCache that reads from the shared cache (SAB or in-process) and returns pre-resolved thenables for synchronous use() consumption
  • cache/client.mjs — Browser-side useCache gains hydration support: reads from self.__react_server_request_cache_entries__ (injected inline script), deserializes with syncFromBuffer, and returns stable pre-resolved thenables

SSR handlers — Per-request cache lifecycle

  • lib/dev/ssr-handler.mjs and lib/start/ssr-handler.mjs — Create a per-request StorageCache + shared cache (SAB or in-process) and inject them into the rendering context via REQUEST_CACHE_CONTEXT / REQUEST_CACHE_SHARED symbols

server/render-dom.mjs — SSR worker integration

  • Attaches the shared cache reader in the worker thread via ContextStorage and a dedicated RequestCacheStorage ALS
  • Serializes resolved cache entries into inline <script> tags for browser hydration, using Object.assign for incremental injection within streamed Suspense boundaries
  • Entries marked with no-hydrate are excluded from the inline script

lib/plugins/use-cache-inline.mjs — Build plugin

  • Recognizes "use cache: request" in SSR environment builds (not just client)
  • Parses the new no-hydrate bare flag and hydrate=false parameter
  • Emits synchronous wrapper functions for request-provider cache calls on the client/SSR side (avoids unnecessary async overhead for pre-resolved thenables)

server/request-cache-context.mjs (new)

  • Standalone AsyncLocalStorage for the request cache reader, independent of the main ContextStorage chain — ensures cache modules find the reader even when the ALS chain breaks across bundled edge modules

no-hydrate directive

Functions can opt out of browser hydration while still benefiting from RSC↔SSR deduplication:

  async function getServerOnlyData() {                  
    "use cache: request; no-hydrate";    
return { secret: process.env.API_KEY };
}

The cached value is shared between RSC and SSR during rendering, but is not embedded in the HTML — the client component will recompute the value in the browser.

Documentation

  • Updated English and Japanese caching documentation with comprehensive coverage of request-scoped caching, cross-environment deduplication, hydration behavior, and the no-hydrate directive

Tests

7 new integration tests covering:

  • Same-value deduplication within a single request
  • Different values across separate requests (request-scoped, not persistent)
  • Client component reading RSC-cached values without prop-drilling
  • Date type preservation across RSC/SSR/client boundaries
  • Hydrated content matching SSR output in the browser
  • no-hydrate directive excluding cache entries from inline scripts
  • Streamed Suspense boundaries with incremental cache entry injection
Author
Viktor Lázár <lazarv1982@gmail.com>
Date
Commit
196ba2e8acbf6250c713178e3ea201f59d946683
35 files changed+3095 -559