Skip to content

Commit 1768c79

Browse files
justin808claude
andcommitted
Document defer script impact on streaming and Selective Hydration
Add comprehensive documentation explaining why deferred scripts should not be used with streaming server rendering, as they defeat React 18's Selective Hydration feature. Key improvements: - Added detailed section to streaming-server-rendering.md explaining the interaction between defer attribute and Selective Hydration - Updated Pro dummy app comment to clarify 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 migration path from defer to async with Shakapacker 8.2+ Breaking changes: None Security implications: None This affects: - Existing installations: Clarifies best practices but doesn't change behavior - New installations: Provides clear guidance on script loading strategies 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 301a432 commit 1768c79

File tree

3 files changed

+65
-2
lines changed

3 files changed

+65
-2
lines changed

docs/building-features/streaming-server-rendering.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,3 +211,61 @@ Streaming SSR is particularly valuable in specific scenarios. Here's when to con
211211
- Prioritize critical data that should be included in the initial HTML
212212
- Use streaming for supplementary data that can load progressively
213213
- 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 -->
227+
<%= javascript_pack_tag('client-bundle', defer: true) %>
228+
```
229+
230+
With `defer: true`, your streamed components will:
231+
232+
1. Arrive progressively in the HTML stream
233+
2. Be visible to users immediately
234+
3. But remain non-interactive until the ENTIRE page finishes streaming
235+
4. Only then will they hydrate
236+
237+
#### Recommended Approaches
238+
239+
**For Pages WITH Streaming Components:**
240+
241+
```erb
242+
<!-- ✅ GOOD: No defer - allows Selective Hydration to work -->
243+
<%= javascript_pack_tag('client-bundle', 'data-turbo-track': 'reload', defer: false) %>
244+
245+
<!-- ✅ BEST: Use async for even faster hydration (requires Shakapacker 8.2+) -->
246+
<%= javascript_pack_tag('client-bundle', 'data-turbo-track': 'reload', async: true) %>
247+
```
248+
249+
**For Pages WITHOUT Streaming Components:**
250+
251+
```erb
252+
<!-- ✅ OK: defer is fine when not using streaming -->
253+
<%= javascript_pack_tag('client-bundle', 'data-turbo-track': 'reload', defer: true) %>
254+
```
255+
256+
#### Why Async is Better Than No Defer
257+
258+
With Shakapacker 8.2+, using `async: true` provides the best performance:
259+
260+
- **No defer/async**: Scripts block HTML parsing and streaming
261+
- **defer: true**: Scripts wait for complete page load (defeats Selective Hydration)
262+
- **async: true**: Scripts load in parallel and execute ASAP, enabling:
263+
- Selective Hydration to work immediately
264+
- Components to become interactive as they stream in
265+
- Optimal Time to Interactive (TTI)
266+
267+
#### Migration Timeline
268+
269+
1. **Before Shakapacker 8.2**: Use `defer: false` for streaming pages
270+
2. **After Shakapacker 8.2**: Migrate to `async: true` for streaming pages
271+
3. **Non-streaming pages**: Can continue using `defer: true` safely

react_on_rails_pro/spec/dummy/app/views/layouts/application.html.erb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@
2424
media: 'all',
2525
'data-turbo-track': 'reload') %>
2626

27-
<%# Used for testing purposes to simulate hydration failure %>
27+
<%# defer: false is required because this app uses streaming server rendering.
28+
Deferred scripts delay hydration until the entire page finishes streaming,
29+
defeating React 18's Selective Hydration. See docs/building-features/streaming-server-rendering.md
30+
skip_js_packs param is used for testing purposes to simulate hydration failure %>
2831
<% unless params[:skip_js_packs] == 'true' %>
2932
<%= javascript_pack_tag('client-bundle', 'data-turbo-track': 'reload', defer: false) %>
3033
<% end %>

spec/dummy/app/views/layouts/application.html.erb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@
99

1010
<%= yield :head %>
1111

12-
<!-- NOTE: Must use defer and not async to keep async scripts loading in correct order -->
12+
<%# defer: true is safe here because this app does NOT use streaming server rendering.
13+
For apps with streaming components, use defer: false or async: true to enable
14+
React 18's Selective Hydration. See docs/building-features/streaming-server-rendering.md %>
1315
<%= javascript_pack_tag('client-bundle', 'data-turbo-track': 'reload', defer: true) %>
1416

1517
<%= csrf_meta_tags %>

0 commit comments

Comments
 (0)