Under the strict production CSP, /hello_server renders but the LikeButton
client island does not hydrate. Can this starter fix that safely in app code, or
does it require an upstream React on Rails Pro change?
The real fix belongs upstream in React on Rails Pro. The app already generates a
per-request railsContext.cspNonce, Rails pack tags receive that nonce, and Pro's
own RSC payload-injection scripts receive that nonce. The missing piece is the
React HTML streaming bootstrap emitted by renderToPipeableStream.
React supports a nonce option for renderToPipeableStream. Pro's streaming
renderer has access to railsContext.cspNonce, but the current call passes only
the stream callbacks and identifierPrefix. That means React's inline $RC
bootstrap script is emitted without a nonce and is blocked by:
script-src 'self' 'nonce-...'
- Streaming SSR for the
renderToPipeableStreammodel and immediate hydration behavior. - React Server Components in React on Rails Pro for the Pro RSC feature overview and requirements.
- React Server Components rendering flow for the HTML stream plus embedded RSC payload lifecycle.
- Pro troubleshooting for the RSC hydration symptoms this spike narrows down.
- Upgrading React on Rails Pro for release notes that can remove local RSC payload template workarounds.
sequenceDiagram
participant Rails
participant View as Rails view helper
participant Pro as React on Rails Pro
participant React as React renderToPipeableStream
participant Browser
Rails->>View: Generate railsContext.cspNonce
View->>Browser: Pack script tags include nonce
View->>Pro: stream_react_component with railsContext
Pro->>Browser: RSC payload injection scripts include nonce
Pro->>React: Start HTML stream
React-->>Browser: Inline streaming bootstrap needs same nonce
Note over React,Browser: Current upstream gap: bootstrap nonce is missing
With the RSC manifests present, /hello_server renders and exposes the
remaining CSP issue. Fetching the route locally shows the nonce split:
- external Rails pack scripts have
nonce="..."; - Pro's component-loaded, console replay, and embedded RSC payload scripts have
nonce="..."; - JSON data script tags do not need a nonce;
- React's final inline
$RC=function(...)...script has no nonce.
The relevant source split is:
react_on_railsaddscspNoncetorailsContext;react_on_rails_propasses that nonce into its RSC payload injection helper;react_on_rails_prodoes not pass that nonce into React'srenderToPipeableStreamoptions for the HTML stream.
- Route-specific
unsafe-inlineCSP: rejected. It would make the demo interactive by weakening the exact production policy this starter is trying to keep strict. - CSP hashes: not viable for the streaming bootstrap. The script is emitted during React streaming and includes request-specific DOM IDs.
- App-level Webpack alias or monkey patch around
renderToPipeableStream: rejected as a starter fix. It would rely on mutable renderer-side state or a local copy of Pro internals and would be fragile under concurrent renderer requests. pnpm patchofreact-on-rails-pro: rejected for the starter. It would patch proprietary package output in the app repo and hide the real upstream contract that Pro should own.- RSC-as-data via a TanStack loader: viable for the public centerpiece route,
but it avoids the streaming HTML bootstrap rather than fixing
stream_react_component.
Tracked upstream as shakacode/react_on_rails#3491.
React on Rails Pro should pass the request nonce into the HTML stream renderer:
renderToPipeableStream(reactRenderedElement, {
nonce: railsContext.cspNonce,
identifierPrefix: domNodeId,
// existing callbacks...
});After upgrading Pro, the regression should be verified by running the Rspack
default under production CSP and clicking /hello_server's LikeButton from
0 likes to 1 like with no browser CSP console errors. The Webpack bridge can
remain as an optional comparison check.
Keep the strict CSP. Do not add unsafe-inline for /hello_server.
For the public demo, use the TanStack-loader RSC composition path as the public
centerpiece because it fetches the RSC payload as data and does not depend on
React's inline streaming bootstrap. Keep /hello_server as the lower-level
streaming reference route and document that its client island remains
server-rendered-only under the production CSP until the Pro nonce fix ships.