Skip to content

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

Merged
lazarv merged 1 commit intomainfrom
feat/use-cache-request-ssr
Mar 26, 2026
Merged

feat: use cache request provider support in ssr layer#368
lazarv merged 1 commit intomainfrom
feat/use-cache-request-ssr

Conversation

@lazarv
Copy link
Copy Markdown
Owner

@lazarv lazarv commented Mar 26, 2026

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

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Mar 26, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
react-server-docs 0a2172f Mar 26 2026, 01:26 PM

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 26, 2026

⚡ Benchmark Results

PR 0a2172f main 27d0d1e
Config 50 connections, 10s/test 50 connections, 10s/test
Benchmark Req/s vs main Avg Latency vs main P99 Latency Throughput
minimal 1264 🔴 -8.3% 38.96 ms 🔴 +9.0% 82 ms 0.7 MB/s
small 1375 🔴 -6.2% 35.85 ms 🔴 +6.7% 61 ms 1.2 MB/s
medium 349 🟢 +4.5% 142.12 ms 🟢 -3.9% 215 ms 5.1 MB/s
large 33 🟢 +8.1% 1468.88 ms 🟢 -1.8% 2735 ms 3.4 MB/s
deep 899 🟢 +1.1% 54.94 ms 🟢 -1.4% 91 ms 3.0 MB/s
wide 43 🟢 +7.9% 1115.3 ms 🟢 -4.6% 2179 ms 2.3 MB/s
cached 3570 🔴 -13.0% 13.45 ms 🔴 +15.1% 27 ms 52.1 MB/s
client-min 942 🔴 -5.2% 52.5 ms 🔴 +5.6% 85 ms 2.4 MB/s
client-small 970 🔴 -2.1% 50.99 ms 🔴 +2.0% 77 ms 2.8 MB/s
client-med 588 🔴 -2.7% 84.38 ms 🔴 +3.0% 123 ms 9.8 MB/s
client-large 98 🟢 +6.9% 497.9 ms 🟢 -6.6% 931 ms 10.1 MB/s
client-deep 855 ⚪ -0.5% 57.93 ms ⚪ +0.7% 87 ms 4.5 MB/s
client-wide 171 ⚪ -0.3% 287.35 ms ⚪ -0.1% 489 ms 9.7 MB/s
static-json 6937 🔴 -13.1% 6.7 ms 🔴 +15.7% 17 ms 2.9 MB/s
static-js 6883 🔴 -10.9% 6.72 ms 🔴 +13.9% 17 ms 32.0 MB/s
404-miss 4957 🔴 -36.2% 9.55 ms 🔴 +66.4% 20 ms 0.6 MB/s
Legend

🟢 > 1% improvement | 🔴 > 1% regression | ⚪ within noise margin

Benchmarks run on GitHub Actions runners (shared infrastructure) — expect ~5% variance between runs. Consistent directional changes across multiple routes are more meaningful than any single number.

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Mar 26, 2026

Codecov Report

❌ Patch coverage is 80.48780% with 8 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@7e068d2). Learn more about missing BASE report.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
packages/rsc/client/shared.mjs 72.22% 5 Missing ⚠️
packages/rsc/server/shared.mjs 86.95% 3 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main     #368   +/-   ##
=======================================
  Coverage        ?   93.93%           
=======================================
  Files           ?        2           
  Lines           ?     2275           
  Branches        ?      686           
=======================================
  Hits            ?     2137           
  Misses          ?      138           
  Partials        ?        0           
Flag Coverage Δ
rsc 93.93% <80.48%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@lazarv lazarv force-pushed the feat/use-cache-request-ssr branch from e241a72 to 0a2172f Compare March 26, 2026 13:25
@lazarv lazarv merged commit 196ba2e into main Mar 26, 2026
155 of 158 checks passed
@lazarv lazarv deleted the feat/use-cache-request-ssr branch March 26, 2026 14:01
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