Skip to content

Commit ac30ea9

Browse files
committed
Add OSS hydrate_on scheduling
1 parent 848e23b commit ac30ea9

17 files changed

Lines changed: 739 additions & 28 deletions

File tree

docs/oss/api-reference/view-helpers-api.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ Once the bundled files have been generated in your `app/assets/webpack` folder,
1111
```ruby
1212
react_component(component_name,
1313
props: {},
14-
prerender: nil)
14+
prerender: nil,
15+
hydrate_on: :immediate,
1516
html_options: {})
1617
```
1718

@@ -36,6 +37,7 @@ Uncommonly used options:
3637
- **Environment override:** set `REACT_ON_RAILS_PRERENDER_OVERRIDE=true|false` to force prerendering on or off globally.
3738
Precedence is: `REACT_ON_RAILS_PRERENDER_OVERRIDE` > component option (`prerender:`) > initializer default (`config.prerender`).
3839
- **auto_load_bundle:** will automatically load the bundle for component by calling `append_javascript_pack_tag` and `append_stylesheet_pack_tag` under the hood.
40+
- **hydrate_on:** controls when React hydrates or client-renders this root. Supported OSS modes are `:immediate` (default), `:visible`, and `:idle`. See [Hydration Scheduling](../building-features/hydration-scheduling.md). `:interaction` is not supported. Deferred modes are OSS-only; React on Rails Pro currently accepts only `:immediate`.
3941
- **id:** Id for the div, will be used to attach the React component. This will get assigned automatically if you do not provide an id. Must be unique.
4042
- **html_options:** Any other HTML options get placed on the added div for the component. For example, you can set a class (or inline style) on the outer div so that it behaves like a span, with the styling of `display:inline-block`. You may also use an option of `tag: "span"` to replace the use of the default DIV tag to be a SPAN tag.
4143
- **trace:** set to true to print additional debugging information in the browser. Defaults to true for development, off otherwise. Only on the **client side** will you will see the `railsContext` and your props.
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Hydration Scheduling
2+
3+
`hydrate_on` lets a Rails view keep server-rendered HTML visible immediately while deferring when React attaches event handlers and effects for that island.
4+
5+
Deferred modes are implemented by the open-source client renderer. If React on Rails Pro is installed, non-immediate modes raise an error instead of silently hydrating immediately.
6+
7+
Use it on standard `react_component` roots:
8+
9+
```erb
10+
<%= react_component("ProductRecommendations",
11+
props: @recommendation_props,
12+
prerender: true,
13+
hydrate_on: :visible) %>
14+
```
15+
16+
The open-source package supports these modes:
17+
18+
| Mode | Behavior |
19+
| ------------ | --------------------------------------------------------------------------------------------------------------- |
20+
| `:immediate` | Default. Hydrates or client-renders as soon as React on Rails processes the component. |
21+
| `:visible` | Uses `IntersectionObserver` and hydrates when the component container enters the viewport, with a 200px margin. |
22+
| `:idle` | Uses `requestIdleCallback` with a timeout, falling back to a short timer when the browser lacks idle callbacks. |
23+
24+
## When to Use It
25+
26+
Use `hydrate_on: :visible` for below-the-fold islands such as recommendations, comments, footers, or sidebar widgets. Use `hydrate_on: :idle` for low-priority islands that are visible but not needed for the first interaction.
27+
28+
Keep `:immediate` for navigation, forms, buttons, and anything the user may interact with right away.
29+
30+
## Server-Rendered Islands
31+
32+
With `prerender: true`, the user sees the server-rendered HTML immediately. React hydration waits for the selected mode:
33+
34+
```erb
35+
<%= react_component("Reviews",
36+
props: { product_id: @product.id },
37+
prerender: true,
38+
hydrate_on: :visible) %>
39+
```
40+
41+
Because the HTML is already present, this works well for content that should be readable before it becomes interactive. Avoid making deferred islands look interactive before hydration. For example, render controls disabled until the component has hydrated, or keep immediate hydration for controls above the fold.
42+
43+
## Client-Only Roots
44+
45+
`hydrate_on` also schedules client-only roots:
46+
47+
```erb
48+
<%= react_component("ClientOnlyChart",
49+
props: @chart_props,
50+
prerender: false,
51+
hydrate_on: :idle) %>
52+
```
53+
54+
For `prerender: false`, the container stays empty until React renders. Prefer server rendering when users should see meaningful content before hydration.
55+
56+
## Turbo and Turbolinks Cleanup
57+
58+
React on Rails cancels pending `:visible` observers and `:idle` callbacks during the same page-unload lifecycle it uses to unmount React roots on Turbo and Turbolinks navigation. If a user navigates away before a deferred island hydrates, the pending observer or callback is disconnected so the old page cannot hydrate after Turbo swaps the body.
59+
60+
## What This Does Not Do
61+
62+
`hydrate_on` does not defer JavaScript bundle fetching. If you use `auto_load_bundle: true`, the generated bundle is still loaded when the page loads; only the React root creation is scheduled. Deferred bundle fetch is a separate optimization.
63+
64+
`:interaction` is not supported in the open-source package. Passing `hydrate_on: :interaction` raises an error. Use `:immediate`, `:visible`, or `:idle`.
65+
66+
Renderer functions, the 3-argument functions that call `hydrateRoot` or `createRoot` themselves, own their own mounting behavior. `hydrate_on` applies to normal React component registrations that React on Rails mounts for you.

docs/sidebars.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ const sidebars: SidebarsConfig = {
7070
label: 'Rendering',
7171
items: [
7272
'building-features/code-splitting',
73+
'building-features/hydration-scheduling',
7374
'building-features/react-helmet',
7475
'building-features/react-19-native-metadata',
7576
'building-features/react-19-activity',

llms-full.txt

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5415,6 +5415,78 @@ And compare:
54155415
- [spec/dummy/client/app/loadable/loadable-server.imports-hmr.jsx](https://github.com/shakacode/react_on_rails/blob/main/react_on_rails_pro/spec/dummy/client/app/loadable/loadable-server.imports-hmr.jsx)
54165416
- [spec/dummy/client/app/loadable/loadable-server.imports-loadable.jsx](https://github.com/shakacode/react_on_rails/blob/main/react_on_rails_pro/spec/dummy/client/app/loadable/loadable-server.imports-loadable.jsx)
54175417

5418+
================================================================================
5419+
PAGE: https://reactonrails.com/docs/building-features/hydration-scheduling
5420+
SOURCE: docs/oss/building-features/hydration-scheduling.md
5421+
================================================================================
5422+
5423+
# Hydration Scheduling
5424+
5425+
`hydrate_on` lets a Rails view keep server-rendered HTML visible immediately while deferring when React attaches event handlers and effects for that island.
5426+
5427+
Deferred modes are implemented by the open-source client renderer. If React on Rails Pro is installed, non-immediate modes raise an error instead of silently hydrating immediately.
5428+
5429+
Use it on standard `react_component` roots:
5430+
5431+
```erb
5432+
<%= react_component("ProductRecommendations",
5433+
props: @recommendation_props,
5434+
prerender: true,
5435+
hydrate_on: :visible) %>
5436+
```
5437+
5438+
The open-source package supports these modes:
5439+
5440+
| Mode | Behavior |
5441+
| ------------ | --------------------------------------------------------------------------------------------------------------- |
5442+
| `:immediate` | Default. Hydrates or client-renders as soon as React on Rails processes the component. |
5443+
| `:visible` | Uses `IntersectionObserver` and hydrates when the component container enters the viewport, with a 200px margin. |
5444+
| `:idle` | Uses `requestIdleCallback` with a timeout, falling back to a short timer when the browser lacks idle callbacks. |
5445+
5446+
## When to Use It
5447+
5448+
Use `hydrate_on: :visible` for below-the-fold islands such as recommendations, comments, footers, or sidebar widgets. Use `hydrate_on: :idle` for low-priority islands that are visible but not needed for the first interaction.
5449+
5450+
Keep `:immediate` for navigation, forms, buttons, and anything the user may interact with right away.
5451+
5452+
## Server-Rendered Islands
5453+
5454+
With `prerender: true`, the user sees the server-rendered HTML immediately. React hydration waits for the selected mode:
5455+
5456+
```erb
5457+
<%= react_component("Reviews",
5458+
props: { product_id: @product.id },
5459+
prerender: true,
5460+
hydrate_on: :visible) %>
5461+
```
5462+
5463+
Because the HTML is already present, this works well for content that should be readable before it becomes interactive. Avoid making deferred islands look interactive before hydration. For example, render controls disabled until the component has hydrated, or keep immediate hydration for controls above the fold.
5464+
5465+
## Client-Only Roots
5466+
5467+
`hydrate_on` also schedules client-only roots:
5468+
5469+
```erb
5470+
<%= react_component("ClientOnlyChart",
5471+
props: @chart_props,
5472+
prerender: false,
5473+
hydrate_on: :idle) %>
5474+
```
5475+
5476+
For `prerender: false`, the container stays empty until React renders. Prefer server rendering when users should see meaningful content before hydration.
5477+
5478+
## Turbo and Turbolinks Cleanup
5479+
5480+
React on Rails cancels pending `:visible` observers and `:idle` callbacks during the same page-unload lifecycle it uses to unmount React roots on Turbo and Turbolinks navigation. If a user navigates away before a deferred island hydrates, the pending observer or callback is disconnected so the old page cannot hydrate after Turbo swaps the body.
5481+
5482+
## What This Does Not Do
5483+
5484+
`hydrate_on` does not defer JavaScript bundle fetching. If you use `auto_load_bundle: true`, the generated bundle is still loaded when the page loads; only the React root creation is scheduled. Deferred bundle fetch is a separate optimization.
5485+
5486+
`:interaction` is not supported in the open-source package. Passing `hydrate_on: :interaction` raises an error. Use `:immediate`, `:visible`, or `:idle`.
5487+
5488+
Renderer functions, the 3-argument functions that call `hydrateRoot` or `createRoot` themselves, own their own mounting behavior. `hydrate_on` applies to normal React component registrations that React on Rails mounts for you.
5489+
54185490
================================================================================
54195491
PAGE: https://reactonrails.com/docs/building-features/react-helmet
54205492
SOURCE: docs/oss/building-features/react-helmet.md
@@ -16114,7 +16186,8 @@ Once the bundled files have been generated in your `app/assets/webpack` folder,
1611416186
```ruby
1611516187
react_component(component_name,
1611616188
props: {},
16117-
prerender: nil)
16189+
prerender: nil,
16190+
hydrate_on: :immediate,
1611816191
html_options: {})
1611916192
```
1612016193

@@ -16139,6 +16212,7 @@ Uncommonly used options:
1613916212
- **Environment override:** set `REACT_ON_RAILS_PRERENDER_OVERRIDE=true|false` to force prerendering on or off globally.
1614016213
Precedence is: `REACT_ON_RAILS_PRERENDER_OVERRIDE` > component option (`prerender:`) > initializer default (`config.prerender`).
1614116214
- **auto_load_bundle:** will automatically load the bundle for component by calling `append_javascript_pack_tag` and `append_stylesheet_pack_tag` under the hood.
16215+
- **hydrate_on:** controls when React hydrates or client-renders this root. Supported OSS modes are `:immediate` (default), `:visible`, and `:idle`. See [Hydration Scheduling](../building-features/hydration-scheduling.md). `:interaction` is not supported. Deferred modes are OSS-only; React on Rails Pro currently accepts only `:immediate`.
1614216216
- **id:** Id for the div, will be used to attach the React component. This will get assigned automatically if you do not provide an id. Must be unique.
1614316217
- **html_options:** Any other HTML options get placed on the added div for the component. For example, you can set a class (or inline style) on the outer div so that it behaves like a span, with the styling of `display:inline-block`. You may also use an option of `tag: "span"` to replace the use of the default DIV tag to be a SPAN tag.
1614416218
- **trace:** set to true to print additional debugging information in the browser. Defaults to true for development, off otherwise. Only on the **client side** will you will see the `railsContext` and your props.

llms.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ Use this file as the short machine-readable route map. Use `./llms-full.txt` for
4747
- JS configuration: https://reactonrails.com/docs/building-features/node-renderer/js-configuration
4848
- View transitions (experimental, unsupported):
4949
- View Transitions recipe + Turbo interplay: https://reactonrails.com/docs/building-features/view-transitions
50+
- Hydration scheduling:
51+
- Defer React root hydration/rendering until immediate, visible, or idle: https://reactonrails.com/docs/building-features/hydration-scheduling
5052
- Configuration and deployment:
5153
- Configuration overview: https://reactonrails.com/docs/configuration
5254
- Pro configuration: https://reactonrails.com/docs/configuration/configuration-pro

0 commit comments

Comments
 (0)