You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Document defer script impact on streaming and Selective Hydration (#1927)
## Summary
- Added comprehensive documentation to streaming-server-rendering.md
explaining why deferred scripts should not be used with streaming server
rendering
- Updated Pro dummy app comment to clarify that `defer: false` is
required for streaming (not just for testing hydration failure)
- Updated main dummy app comment to explain `defer: true` is safe there
because no streaming is used
- Documented the migration path from defer to async with Shakapacker
8.2+
## Key Improvements
- **Documentation**: Added "Script Loading Strategy for Streaming"
section explaining:
- How deferred scripts defeat React 18's Selective Hydration
- Proper configuration for streaming vs non-streaming pages
- Migration path to async scripts with Shakapacker 8.2+
- **Code Comments**: Updated both dummy apps with clear, educational
comments explaining:
- Why Pro dummy uses `defer: false` (streaming components present)
- Why main dummy uses `defer: true` (no streaming components)
- Links to documentation for more details
## Test Plan
- [x] Ran dummy app specs - all passing
- [x] RuboCop passed with no violations
- [x] Prettier formatting verified
- [x] Pre-commit hooks passed
## Breaking Changes
None. This is purely documentation and clarifying comments.
## Security Implications
None.
## Impact
- **Existing installations**: Clarifies best practices but doesn't
change behavior
- **New installations**: Provides clear guidance on script loading
strategies for streaming
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- Reviewable:start -->
- - -
This change is [<img src="https://reviewable.io/review_button.svg"
height="34" align="absmiddle"
alt="Reviewable"/>](https://reviewable.io/reviews/shakacode/react_on_rails/1927)
<!-- Reviewable:end -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Documentation**
* Added comprehensive guidance on script-loading strategies for
streaming server rendering, explaining why defer can break streaming
hydration and when to prefer async for faster hydration; covers
migration timelines and per-page/component fallbacks.
* Added an important note that certain Redux shared-store setups with
inline registration require defer to avoid registration failures.
* **Bug Fixes / Layouts**
* Layouts updated to use async loading by default while retaining defer
for pages that need Redux shared-store ordering.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: docs/api-reference/redux-store-api.md
+4Lines changed: 4 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,6 +4,10 @@
4
4
>
5
5
> This Redux API is no longer recommended as it prevents dynamic code splitting for performance. Instead, you should use the standard `react_component` view helper passing in a "Render-Function."
6
6
7
+
> [!IMPORTANT]
8
+
>
9
+
> **Script Loading Requirement:** If you use Redux shared stores with inline component registration (registering components in view templates with `<script>ReactOnRails.register({ MyComponent })</script>`), you **must use `defer: true`** in your `javascript_pack_tag` instead of `async: true`. With async loading, the bundle may execute before inline scripts, causing component registration failures. See the [Streaming Server Rendering documentation](../building-features/streaming-server-rendering.md#important-redux-shared-store-caveat) for details and alternatives.
10
+
7
11
You don't need to use the `redux_store` api to use Redux. This API was set up to support multiple calls to `react_component` on one page that all talk to the same Redux store.
8
12
9
13
If you are only rendering one React component on a page, as is typical to do a "Single Page App" in React, then you should _probably_ pass the props to your React component in a "Render-Function."
Copy file name to clipboardExpand all lines: docs/building-features/streaming-server-rendering.md
+126Lines changed: 126 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -211,3 +211,129 @@ Streaming SSR is particularly valuable in specific scenarios. Here's when to con
211
211
- Prioritize critical data that should be included in the initial HTML
212
212
- Use streaming for supplementary data that can load progressively
213
213
- Consider implementing a waterfall strategy for dependent data
214
+
215
+
### Script Loading Strategy for Streaming
216
+
217
+
**IMPORTANT**: When using streaming server rendering, you should NOT use `defer: true` for your JavaScript pack tags. Here's why:
218
+
219
+
#### Understanding the Problem with Defer
220
+
221
+
Deferred scripts (`defer: true`) only execute after the entire HTML document has finished parsing and streaming. This defeats the key benefit of React 18's Selective Hydration feature, which allows streamed components to hydrate as soon as they arrive—even while other parts of the page are still streaming.
222
+
223
+
**Example Problem:**
224
+
225
+
```erb
226
+
<!-- ❌ BAD: This delays hydration for ALL streamed components -->
Note: `async: true` with the `immediate_hydration` feature allows components to hydrate during page load, improving TTI even without streaming. See the Immediate Hydration section below for configuration details.
259
+
260
+
**⚠️ Important: Redux Shared Store Caveat**
261
+
262
+
If you are using Redux shared stores with the `redux_store` helper and **inline script registration** (registering components in view templates with `<script>ReactOnRails.register({ MyComponent })</script>`), you must use `defer: true` instead of `async: true`:
263
+
264
+
```erb
265
+
<!-- ⚠️ REQUIRED for Redux shared stores with inline registration -->
**Why?** With `async: true`, the bundle executes immediately upon download, potentially **before** inline `<script>` tags in the HTML execute. This causes component registration failures when React on Rails tries to hydrate the component.
270
+
271
+
**Solutions:**
272
+
273
+
1.**Use `defer: true`** - Ensures proper execution order (inline scripts run before bundle)
274
+
2.**Move registration to bundle** - Register components in your JavaScript bundle instead of inline scripts (recommended)
275
+
3.**Use React on Rails Pro** - Pro's `getOrWaitForStore` and `getOrWaitForStoreGenerator` can handle async loading with inline registration
276
+
277
+
See the [Redux Store API documentation](../api-reference/redux-store-api.md) for more details on Redux shared stores.
278
+
279
+
#### Why Async is Better Than No Defer
280
+
281
+
With Shakapacker ≥ 8.2.0, using `async: true` provides the best performance:
282
+
283
+
-**No defer/async**: Scripts block HTML parsing and streaming
-**async: true**: Scripts load in parallel and execute ASAP, enabling:
286
+
- Selective Hydration to work immediately
287
+
- Components to become interactive as they stream in
288
+
- Optimal Time to Interactive (TTI)
289
+
290
+
#### Migration Timeline
291
+
292
+
1.**Before Shakapacker 8.2.0**: Use `defer: false` for streaming pages
293
+
2.**Shakapacker ≥ 8.2.0**: Migrate to `async: true` for all pages (streaming and non-streaming)
294
+
3.**Enable `immediate_hydration`**: Configure for optimal Time to Interactive (see section below)
295
+
296
+
#### Configuring Immediate Hydration
297
+
298
+
React on Rails Pro supports the `immediate_hydration` feature, which allows components to hydrate during the page loading state (before DOMContentLoaded). This works optimally with `async: true` scripts:
299
+
300
+
```ruby
301
+
# config/initializers/react_on_rails.rb
302
+
ReactOnRails.configure do |config|
303
+
config.immediate_hydration =true# Enable early hydration
0 commit comments