Skip to content
Merged
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
20 changes: 15 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ After a release, please make sure to run `bundle exec rake update_changelog`. Th

Changes since the last non-beta release.

### [15.0.0] - 2025-08-28
### [16.0.0] - 2025-01-XX

See [Release Notes](docs/release-notes/15.0.0.md) for full details.
See [Release Notes](docs/release-notes/16.0.0.md) for full details.

### Removed (Breaking Changes)

Expand All @@ -40,10 +40,14 @@ See [Release Notes](docs/release-notes/15.0.0.md) for full details.
- For TypeScript errors, upgrade to TypeScript 5.8+ and set `module` to `nodenext`.
- `ReactOnRails.reactOnRailsPageLoaded` is now an async function. Migration:
- Add `await` when calling this function: `await ReactOnRails.reactOnRailsPageLoaded()`.
- `force_load` configuration now defaults to `true`. Migration:
- Set `force_load: false` in your config if you want the previous behavior.
- **RENAMED**: `force_load` configuration renamed to `immediate_hydration` for better API clarity.
- `immediate_hydration` now defaults to `false` and requires React on Rails Pro license.
- Migration:
- `config.force_load = true` → `config.immediate_hydration = true`
- `react_component(force_load: true)` → `react_component(immediate_hydration: true)`
- `redux_store(force_load: true)` → `redux_store(immediate_hydration: true)`

For detailed migration instructions, see the [15.0.0 Release Notes](docs/release-notes/15.0.0.md).
For detailed migration instructions, see the [16.0.0 Release Notes](docs/release-notes/16.0.0.md).

#### Fixed

Expand All @@ -70,6 +74,12 @@ For detailed migration instructions, see the [15.0.0 Release Notes](docs/release
- React Server Components Support (Pro Feature) [PR 1644](https://github.com/shakacode/react_on_rails/pull/1644) by [AbanoubGhadban](https://github.com/AbanoubGhadban).
- Improved component and store hydration performance [PR 1656](https://github.com/shakacode/react_on_rails/pull/1656) by [AbanoubGhadban](https://github.com/AbanoubGhadban).

### [15.0.0] - 2025-08-28 - RETRACTED

**⚠️ This version has been retracted due to API design issues. Please upgrade directly to v16.0.0.**

The `force_load` feature was incorrectly available without a Pro license and has been renamed to `immediate_hydration` for better clarity. All features from v15 are available in v16 with the corrected API.

### [14.2.0] - 2025-03-03

#### Added
Expand Down
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- All linters: `rake lint` (runs ESLint and RuboCop)
- ESLint only: `yarn run lint` or `rake lint:eslint`
- RuboCop only: `rake lint:rubocop`
- **Code Formatting**:
- Format code with Prettier: `yarn start format`
- Check formatting without fixing: `yarn start format.listDifferent`
- **Build**: `yarn run build` (compiles TypeScript to JavaScript in node_package/lib)
- **Type checking**: `yarn run type-check`

Expand Down
36 changes: 36 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,42 @@ cd react_on_rails/
yarn run check
```

## Development Commands

### Code Formatting

To format JavaScript/TypeScript files with Prettier:

```sh
yarn start format
```

To check formatting without fixing:

```sh
yarn start format.listDifferent
```

### Linting

Run all linters (ESLint and RuboCop):

```sh
rake lint
```

Run only RuboCop:

```sh
rake lint:rubocop
```

Run only ESLint:

```sh
yarn run lint
```

### Starting the Dummy App

To run the dummy app, it's **CRITICAL** to not just run `rails s`. You have to run `foreman start` with one of the Procfiles. If you don't do this, then `webpack` will not generate a new bundle, and you will be seriously confused when you change JavaScript and the app does not change. If you change the Webpack configs, then you need to restart Foreman. If you change the JS code for react-on-rails, you need to run `yarn run build` in the project root.
Expand Down
11 changes: 6 additions & 5 deletions docs/guides/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,13 +223,14 @@ ReactOnRails.configure do |config|
# DEPRECATED: Use `generated_component_packs_loading_strategy` instead.
# Migration: `defer_generated_component_packs: true` → `generated_component_packs_loading_strategy: :defer`
# Migration: `defer_generated_component_packs: false` → `generated_component_packs_loading_strategy: :sync`
# See [15.0.0 Release Notes](docs/release-notes/15.0.0.md) for more details.
# See [16.0.0 Release Notes](docs/release-notes/16.0.0.md) for more details.
# config.defer_generated_component_packs = false

# Default is true
# When true, components hydrate immediately as soon as their server-rendered HTML reaches the client,
# without waiting for the full page load. This improves time-to-interactive performance.
config.force_load = true
# Default is false
# React on Rails Pro (licensed) feature: When true, components hydrate immediately as soon as
# their server-rendered HTML reaches the client, without waiting for the full page load.
# This improves time-to-interactive performance.
config.immediate_hydration = false

################################################################################
# I18N OPTIONS
Expand Down
2 changes: 1 addition & 1 deletion docs/guides/streaming-server-rendering.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ React on Rails Pro supports streaming server rendering using React 18's latest A

- React on Rails Pro subscription
- React 19
- React on Rails v15.0.0-alpha.0 or higher
- React on Rails v16.0.0 or higher
- React on Rails Pro v4.0.0.rc.5 or higher

## Benefits of Streaming Server Rendering
Expand Down
2 changes: 1 addition & 1 deletion docs/rails/turbolinks.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ document.addEventListener('turbolinks:load', function () {
React on Rails 15 fixes both issues, so if you still have the listener it can be removed (and should be as `reactOnRailsPageLoaded()` is now async).

> [!WARNING]
> Do not use `force_load: false` with Turbolinks if you have async scripts.
> Do not use `immediate_hydration: false` (React on Rails Pro licensed feature) with Turbolinks if you have async scripts.

## Troubleshooting

Expand Down
141 changes: 2 additions & 139 deletions docs/release-notes/15.0.0.md
Original file line number Diff line number Diff line change
@@ -1,142 +1,5 @@
# React on Rails 15.0.0 Release Notes

Also see the [Changelog for 15.0.0](https://github.com/shakacode/react_on_rails/blob/master/CHANGELOG.md#1500---2025-08-28).
**⚠️ Version 15.0.0 has been retracted. Please upgrade directly to v16.0.0.**

## Major Features

### 🚀 React Server Components Support

Experience the future of React with full RSC integration in your Rails apps:

- Seamlessly use React Server Components
- Reduce client bundle sizes
- Enable powerful new patterns for data fetching
- ⚡️ Requires React on Rails Pro - [See the full tutorial](https://www.shakacode.com/react-on-rails-pro/docs/react-server-components/tutorial/)

### 🚀 Major Performance Breakthrough: Early Hydration

**React on Rails now starts hydration even before the full page is loaded!** This revolutionary change delivers significant performance improvements across all pages:

- **Eliminates Race Conditions**: No more waiting for full page load before hydration begins
- **Faster Time-to-Interactive**: Components hydrate as soon as their server-rendered HTML reaches the client
- **Streaming HTML Optimization**: Perfect for modern streaming responses - components hydrate in parallel with page streaming
- **Async Script Safety**: Can use `async` scripts without fear of race conditions
- **No More Defer Needed**: The previous need for `defer` to prevent race conditions has been eliminated

This optimization is particularly impactful for:

- **Streamed pages** where content loads progressively
- **Large pages** with many components
- **Slow network conditions** where every millisecond counts
- **Modern web apps** requiring fast interactivity

_Performance improvement visualization:_

![Performance comparison showing early hydration improvement](../assets/early-hydration-performance-comparison.jpg)

_The image above demonstrates the dramatic performance improvement:_

- **Left (Before)**: Hydration didn't start until the full page load completed, causing a huge delay before hydration
- **Right (After)**: Hydration starts immediately as soon as components are available, without waiting for full page load
- **Result**: Components now become interactive much faster, eliminating the previous race condition delays

### Enhanced Script Loading Strategies

- New configuration option `generated_component_packs_loading_strategy` replaces `defer_generated_component_packs`
- Supports three loading strategies:
- `:async` - Loads scripts asynchronously (default for Shakapacker ≥ 8.2.0)
- `:defer` - Defers script execution until after page load (doesn't work well with Streamed HTML as it will wait for the full page load before hydrating the components)
- `:sync` - Loads scripts synchronously (default for Shakapacker < 8.2.0) (better to upgrade to Shakapacker 8.2.0 and use `:async` strategy)
- Improves page performance by optimizing how component packs are loaded

## Breaking Changes

### Component Hydration Changes

- The `defer_generated_component_packs` configuration has been deprecated. Use `generated_component_packs_loading_strategy` instead.
- The `generated_component_packs_loading_strategy` defaults to `:async` for Shakapacker ≥ 8.2.0 and `:sync` for Shakapacker < 8.2.0.
- The `force_load` configuration now defaults to `true`.
- The new default values of `generated_component_packs_loading_strategy: :async` and `force_load: true` work together to optimize component hydration. Components now hydrate as soon as their code and server-rendered HTML are available, without waiting for the full page to load. This parallel processing significantly improves time-to-interactive by eliminating the traditional waterfall of waiting for page load before beginning hydration (It's critical for streamed HTML).

- The previous need for deferring scripts to prevent race conditions has been eliminated due to improved hydration handling. Making scripts not defer is critical to execute the hydration scripts early before the page is fully loaded.
- The `force_load` configuration makes `react-on-rails` hydrate components immediately as soon as their server-rendered HTML reaches the client, without waiting for the full page load.
- If you want to keep the previous behavior, you can set `generated_component_packs_loading_strategy: :defer` or `force_load: false` in your `config/initializers/react_on_rails.rb` file.
- You can also keep it for individual components by passing `force_load: false` to `react_component` or `stream_react_component`.
- Redux store now supports `force_load` option, which defaults to `config.force_load` (and so to `true` if that isn't set). If `true`, the Redux store will hydrate immediately as soon as its server-side data reaches the client.
- You can override this behavior for individual Redux stores by calling the `redux_store` helper with `force_load: false`, same as `react_component`.

- `ReactOnRails.reactOnRailsPageLoaded()` is now an async function:

- If you manually call this function to ensure components are hydrated (e.g., with async script loading), you must now await the promise it returns:

```js
// Before
ReactOnRails.reactOnRailsPageLoaded();
// Code expecting all components to be hydrated

// After
await ReactOnRails.reactOnRailsPageLoaded();
// Code expecting all components to be hydrated
```

- If you call it in a `turbolinks:load` listener to work around the issue documented in [Turbolinks](../rails/turbolinks.md#async-script-loading), the listener can be safely removed.

### Script Loading Strategy Migration

- If you were previously using `defer_generated_component_packs: true`, use `generated_component_packs_loading_strategy: :defer` instead
- If you were previously using `defer_generated_component_packs: false`, use `generated_component_packs_loading_strategy: :sync` instead
- For optimal performance with Shakapacker ≥ 8.2.0, consider using `generated_component_packs_loading_strategy: :async`

### ESM-only package

The package is now published as ES Modules instead of CommonJS. In most cases it shouldn't affect your code, as bundlers will be able to handle it. However:

- If you explicitly use `require('react-on-rails')`, and can't change to `import`, upgrade to Node v20.19.0+ or v22.12.0+. They allow `require` for ESM modules without any flags. Node v20.17.0+ with `--experimental-require-module` should work as well.
- If you run into `TS1479: The current file is a CommonJS module whose imports will produce 'require' calls; however, the referenced file is an ECMAScript module and cannot be imported with 'require'.` TypeScript error, you'll need to [upgrade to TypeScript 5.8 and set `module` to `nodenext`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-8.html#support-for-require-of-ecmascript-modules-in---module-nodenext).

Finally, if everything else fails, please contact us and we'll help you upgrade or release a dual ESM-CJS version.

### `globalThis`

[`globalThis`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis) is now used in code.
It should be available in browsers since 2020 and in Node, but in case your environment doesn't support it, you'll need to shim it using [globalthis](https://www.npmjs.com/package/globalthis) or [core-js](https://www.npmjs.com/package/core-js).

## Store Dependencies for Components

When using Redux stores with multiple components, you need to explicitly declare store dependencies to optimize hydration. Here's how:

### The Problem

If you have deferred Redux stores and components like this:

```erb
<% redux_store("SimpleStore", props: @app_props_server_render, defer: true) %>
<%= react_component('ReduxApp', {}, {prerender: true}) %>
<%= react_component('ComponentWithNoStore', {}, {prerender: true}) %>
<%= redux_store_hydration_data %>
```

By default, React on Rails assumes components depend on all previously created stores. This means:

- Neither `ReduxApp` nor `ComponentWithNoStore` will hydrate until `SimpleStore` is hydrated
- Since the store is deferred to the end of the page, both components are forced to wait unnecessarily

### The Solution

Explicitly declare store dependencies for each component:

```erb
<% redux_store("SimpleStore", props: @app_props_server_render, defer: true) %>
<%= react_component('ReduxApp', {}, {
prerender: true
# No need to specify store_dependencies: it automatically depends on SimpleStore
}) %>
<%= react_component('ComponentWithNoStore', {}, {
prerender: true,
# Explicitly declare no store dependencies
store_dependencies: []
}) %>
<%= redux_store_hydration_data %>
```

This allows `ComponentWithNoStore` to hydrate immediately without waiting for `SimpleStore`, improving page performance.
See [React on Rails 16.0.0 Release Notes](./16.0.0.md) for the latest version.
Loading
Loading