Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions docs/building-features/streaming-server-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,3 +211,61 @@ Streaming SSR is particularly valuable in specific scenarios. Here's when to con
- Prioritize critical data that should be included in the initial HTML
- Use streaming for supplementary data that can load progressively
- Consider implementing a waterfall strategy for dependent data

### Script Loading Strategy for Streaming

**IMPORTANT**: When using streaming server rendering, you should NOT use `defer: true` for your JavaScript pack tags. Here's why:

#### Understanding the Problem with Defer

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.

**Example Problem:**

```erb
<!-- ❌ BAD: This delays hydration for ALL streamed components -->
<%= javascript_pack_tag('client-bundle', defer: true) %>
```

With `defer: true`, your streamed components will:

1. Arrive progressively in the HTML stream
2. Be visible to users immediately
3. But remain non-interactive until the ENTIRE page finishes streaming
4. Only then will they hydrate

#### Recommended Approaches

**For Pages WITH Streaming Components:**

```erb
<!-- ✅ GOOD: No defer - allows Selective Hydration to work -->
<%= javascript_pack_tag('client-bundle', 'data-turbo-track': 'reload', defer: false) %>

<!-- ✅ BEST: Use async for even faster hydration (requires Shakapacker ≥ 8.2.0) -->
<%= javascript_pack_tag('client-bundle', 'data-turbo-track': 'reload', async: true) %>
```

**For Pages WITHOUT Streaming Components:**

```erb
<!-- ✅ OK: defer is fine when not using streaming -->
<%= javascript_pack_tag('client-bundle', 'data-turbo-track': 'reload', defer: true) %>
```

#### Why Async is Better Than No Defer

With Shakapacker ≥ 8.2.0, using `async: true` provides the best performance:

- **No defer/async**: Scripts block HTML parsing and streaming
- **defer: true**: Scripts wait for complete page load (defeats Selective Hydration)
- **async: true**: Scripts load in parallel and execute ASAP, enabling:
- Selective Hydration to work immediately
- Components to become interactive as they stream in
- Optimal Time to Interactive (TTI)

#### Migration Timeline

1. **Before Shakapacker 8.2.0**: Use `defer: false` for streaming pages
2. **Shakapacker ≥ 8.2.0**: Migrate to `async: true` for streaming pages
3. **Non-streaming pages**: Can continue using `defer: true` safely (regardless of Shakapacker version)
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
media: 'all',
'data-turbo-track': 'reload') %>

<%# Used for testing purposes to simulate hydration failure %>
<%# defer: false is required because this app uses streaming server rendering.
Deferred scripts delay hydration until the entire page finishes streaming,
defeating React 18's Selective Hydration. See docs/building-features/streaming-server-rendering.md
skip_js_packs param is used for testing purposes to simulate hydration failure %>
<% unless params[:skip_js_packs] == 'true' %>
<%= javascript_pack_tag('client-bundle', 'data-turbo-track': 'reload', defer: false) %>
<% end %>
Expand Down
5 changes: 4 additions & 1 deletion spec/dummy/app/views/layouts/application.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@

<%= yield :head %>

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

<%= csrf_meta_tags %>
Expand Down
Loading