Upgrading React on Rails
Need Help Migrating?
If you would like help in migrating between React on Rails versions or help with implementing server rendering, please contact justin@shakacode.com for more information about our React on Rails Pro Support.
We specialize in helping companies to quickly and efficiently upgrade. The older versions use the Rails asset pipeline to package client assets. The current and recommended way is to use Webpack 4+ for asset preparation. You may also need help migrating from the rails/webpacker's Webpack configuration to a better setup ready for Server Side Rendering.
General Upgrade Process
After upgrading to any major version, always run the generator to get the latest defaults:
rails generate react_on_rails:install
⚠️ Important: Review generated changes carefully before applying to avoid overwriting custom configurations. The generator updates:
bin/dev(improved development workflow)- webpack configurations
shakapacker.ymlsettings- other configuration files
Upgrade Preflight
Before changing versions, check these first:
- Ruby and Node requirements: React on Rails v16 requires Ruby 3.0+ and Node 18+.
- Bundler age: legacy apps may have lockfiles created by Bundler 1.x. Those lockfiles can fail on modern Ruby before the React on Rails upgrade even starts.
- Rails version: current
react_on_railsrequires Rails 5.2+. Rails 5.1 apps need a Rails upgrade before they can bundle v16. - Asset stack: if the app still uses
webpacker, upgrade toshakapackerfirst. - Version pinning: use exact gem and npm package versions for React on Rails-related packages. Avoid
^,~, or*.
If your app is both Ruby/Bundler-old and Webpacker-old, do those upgrades first. Trying to jump directly from a Rails 5 / Webpacker 3 / Bundler 1 stack to current React on Rails is usually more than one migration.
If the first failure is a Bundler 1.x lockfile, refresh that lockfile with Bundler 2.x before changing React on Rails:
gem install bundler # if Bundler 2.x is not already available
bundle lock --update
bundle install
Upgrading Precompile Hooks for SSR + HMR
If your app uses server-side rendering with HMR, Shakapacker commonly runs two webpack processes during development (client HMR and server watcher). In that setup, direct-command precompile hooks are more fragile because each process can trigger the hook.
Use a script-based hook with an explicit self-guard. This pattern is reliable across Shakapacker versions.
Recommended setup
-
Create
bin/shakapacker-precompile-hook:#!/usr/bin/env ruby
# frozen_string_literal: true
exit 0 if ENV["SHAKAPACKER_SKIP_PRECOMPILE_HOOK"] == "true"
system("bundle", "exec", "rake", "react_on_rails:locale", exception: true) -
Make it executable:
chmod +x bin/shakapacker-precompile-hook -
Configure
config/shakapacker.yml:default: &default
precompile_hook: 'bin/shakapacker-precompile-hook'
Version guidance
- Shakapacker 9.4.0+: Native support for
SHAKAPACKER_SKIP_PRECOMPILE_HOOKexists. - Shakapacker 9.0-9.3: Prefer script-based hooks with the self-guard for reliable behavior.
- All supported versions: Script-based hooks are the safest choice for SSR + HMR development.
CI recommendation
In CI, run precompile preparation explicitly once before webpack compilation or test startup, rather than relying on hook timing in watch-like flows.
Upgrading to v16.4.0 (from v16.3.x)
This release includes many generator, SSR, and RSC fixes. See the CHANGELOG for the full list.
Key actions required:
- Pro users: Legacy license key file removed.
config/react_on_rails_pro_license.keyis no longer read. Move your token to theREACT_ON_RAILS_PRO_LICENSEenvironment variable. - Pro users:
jwt >= 2.7now required. If your Gemfile pinsjwtto an older version (e.g., 2.2.x for OAuth compatibility), you must upgrade it before bundlingreact_on_rails_pro16.4.0. - Pro users: RSC payload template override. If your app overrides
custom_rsc_payload_template, make sure it resolves to a text or format-neutral template (e.g.,.text.erb). HTML-only overrides will raiseActionView::MissingTemplate. - Generator layout renamed. Fresh installs now generate
react_on_rails_default.html.erbinstead ofhello_world.html.erb. Existing apps are unaffected unless re-running the generator. - Pro users: Review changed defaults (see table below).
Changed Pro Defaults in 16.4.0
If you relied on previous defaults without explicitly setting values, behavior will change after upgrading:
| Setting | Old default (3.x / 16.2.x) | New default (16.4.0) |
|---|---|---|
ssr_timeout | 20s | 5s |
renderer_request_retry_limit | 3 | 5 |
renderer_use_fallback_exec_js | false | true |
To preserve old behavior, explicitly set these values in config/initializers/react_on_rails_pro.rb.
Pro Node Renderer: bundlePath → serverBundleCachePath
In your node renderer config file, rename bundlePath to serverBundleCachePath:
- bundlePath: path.resolve(__dirname, '../.node-renderer-bundles'),
+ serverBundleCachePath: path.resolve(__dirname, '../.node-renderer-bundles'),
Pro users upgrading from 3.x: Version numbering
react_on_rails_pro jumped from version 3.3.x to 16.x.x to align with the core gem version. If you are on Pro 3.3.x, version 16.2.0 is your next upgrade target. See the Pro Upgrade Guide for the full migration path (package renames, import changes, license setup).
Upgrading to v16.3.0 (from v16.2.x)
This is a minor release. Update your gem and npm package versions, then run bundle install and your package manager's install command. See the CHANGELOG for details.
Key changes:
- Simplified Shakapacker version handling. Obsolete minimum version checks (6.5.1) were removed. The gemspec dependency
shakapacker >= 6.0is now the only minimum version requirement. - Pro users: License-optional model. Pro now works without a license for evaluation, development, testing, and CI/CD. A paid license is only required for production deployments.
- Pro users: Multiple license plan types. License validation now supports plan types beyond "paid" (startup, nonprofit, education, oss, partner).
Upgrading to v16.2.x (from v16.1.x)
This release focuses on clear separation between open-source and Pro features. See the v16.2.x Release Notes for full details.
Key actions required:
- Remove
config.immediate_hydrationfrom your initializer - this config option has been removed (automatic for Pro users) - Use exact versions in
package.json- semver wildcards (^,~,*) now cause boot failures - Pro users: If importing Pro methods from
react-on-rails, switch toreact-on-rails-pro - RSC users: Move RSC configurations from
ReactOnRails.configuretoReactOnRailsPro.configure
Upgrading to v16.1.x (from v16.0.x)
This is a minor release - update your gem and npm package versions, then run bundle install and your package manager's install command. See the v16.1.x Release Notes for new features and bug fixes.
Deprecation: Remove config.generated_assets_dirs from your configuration if present. Asset paths are now automatically determined from public_output_path in config/shakapacker.yml.
Upgrading to v16 (from v14/v15)
Breaking Changes
- Webpacker support completely removed. Shakapacker >= 6.0 is now required.
- Updated runtime requirements:
- Minimum Ruby version: 3.2
- Minimum Node.js version: 20
- Install generator now validates prerequisites and requires at least one JavaScript package manager
Migration Steps
-
Update Dependencies
Note: The versions below are examples. Check the changelog for the latest stable release and substitute accordingly.
# Gemfile
gem "react_on_rails", "16.4.0"// package.json — use the npm equivalent of the same release
{
"dependencies": {
"react-on-rails": "16.4.0"
}
} -
Install Updates
bundle update react_on_rails shakapacker
# then run your package manager's install command
npm install # or: yarn install / pnpm install -
Run Generator
bundle exec rails generate react_on_rails:install -
Review and Apply Changes
- If your app was still on
webpacker, finish that migration first - Check webpack configuration exports (function naming may have changed)
- Review
shakapacker.ymlsettings - Update
bin/devif needed
- If your app was still on
-
Test Your Application
# Test asset compilation
bundle exec rails assets:precompile
# Test development server
bin/dev
# Run your test suite
bundle exec rspec # or your test command
Common Upgrade Issues
Build Fails with Module Resolution Errors
Symptoms: Webpack cannot find modules referenced in your configuration
Solutions:
- Clear webpack cache:
rm -rf node_modules/.cache - Verify all ProvidePlugin modules exist
- Check webpack alias configuration
For troubleshooting build errors, see the build errors guide.
Enhanced Features in v16
- Enhanced error handling in
react_on_rails:generate_packstask with detailed debugging guidance - Improved development tooling with better error messages and troubleshooting steps
- Better package manager detection with multi-strategy validation
Upgrading to v13
Breaking Change
Previously, the gem webpacker was a Gem dependency.
v13 has changed slightly to switch to shakapacker.
For details, see the Shakapacker guide to upgrading to version 6 and version 7
In summary:
- Change the gem reference from 'webpacker' to 'shakapacker'
- Change the npm reference from '@rails/webpacker' to 'shakapacker'
- Other updates, depending on what version of
rails/webpackerthat you had.
Upgrading to v12
Recent versions
Make sure that you are on a relatively more recent version of Rails and Webpacker. Yes, the rails/webpacker gem is required! v12 is tested on Rails 6. It should work on Rails v5. If you're on any older version, and v12 doesn't work, please file an issue.
Removed Configuration config.symlink_non_digested_assets_regex
Remove config.symlink_non_digested_assets_regex from your config/initializers/react_on_rails.rb.
If you still need that feature, please file an issue.
i18n default format changed to JSON
- If you're using the internationalization helper, then set
config.i18n_output_format = 'js'. You can later update to the default JSON format as you will need to update your usage of that file. A JSON format is more efficient.
Updated API for ReactOnRails.register()
In order to solve the issues regarding React Hooks compatibility, the number of parameters for functions is used to determine if you have a Render-Function that will get invoked to return a React component, or you are registering a React component defined by a function. Please see Render-Functions and the Rails Context for more information on what a Render-Function is.
Update required for registered functions taking exactly 2 params.
Registered Objects are of the following type:
-
Function that takes only zero or one params and returns a React Element, often JSX. If the function takes zero or one params, there is no migration needed for that function.
export default (props) => <Component {...props} />; -
Function that takes only zero or one params and returns an Object (not a React Element). If the function takes zero or one params, you need to add one or two unused params so you have exactly 2 params and then that function will be treated as a render function and it can return an Object rather than a React element. If you don't do this, you'll see this obscure error message:
[SERVER] message: Objects are not valid as a React child (found: object with keys {renderedHtml}). If you meant to render a collection of children, use an array instead.
in YourComponentRenderFunction
So look in YourComponentRenderFunction and do this change
export default (props) => ({
renderedHTML: getRenderedHTML(),
});
To have exactly 2 arguments:
export default (props, _railsContext) => ({
renderedHTML: getRenderedHTML(),
});
- Function that takes 2 params and returns a React function or class component. Migration is needed as the older syntax returned a React Element.
A function component is a function that takes zero or one params and returns a React Element, like JSX. The correct syntax
looks like:
Note, you cannot return a React Element (JSX). See below for the migration steps. If your function that took two params returned an Object, then no migration is required.
export default (props, railsContext) => () => <Component {...{ ...props, railsContext }} />; - Function that takes 3 params and uses the 3rd param,
domNodeId, to callReactDOM.hydrate. If the function takes 3 params, there is no migration needed for that function. - ES6 or ES5 class. There is no migration needed.
Previously, with case number 2, you could return a React Element.
The fix is simple. Here is an example of the change you'll do:

Broken, as this function takes two params and it returns a React Element from a JSX Literal
export default (props, _railsContext) => <Component {...props} />;
If you make this mistake, you'll get this warning
Warning: React.createElement: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: <Fragment />. Did you accidentally export a JSX literal instead of a component?
And this error:
react-dom.development.js:23965 Uncaught Error: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: object.
In this example, you need to wrap the <Component {...props} /> in a function call like this which
results in the return value being a React function component.
export default (props, _railsContext) => () => <Component {...props} />;
If you have a pure component, taking one or zero parameters, and you have an unnecessary function wrapper such that you're returning a function rather than a React Element, then:
- You won't see anything render.
- You will see this warning in development mode:
Warning: Functions are not valid as a React child. This may happen if you return a Component instead of <Component /> from render. Or maybe you meant to call this function rather than return it.
Upgrading rails/webpacker from v3 to v4
Custom Webpack build file
The default value for extract_css is false in config/webpack.yml. Custom Webpack builds should set this value to true, or else no CSS link tags are generated. You have a custom Webpack build if you are not using rails/webpacker to set up your Webpack configuration.
default: &default # other stuff
extract_css: true
# by default, extract and emit a css file. The default is false
Upgrading to version 11
- Remove
server_render_methodfrom config/initializers/react_on_rails.rb. Alternate server rendering methods are part of React on Rails Pro. If you want to use a custom renderer, contact justin@shakacode.com. We have a custom node rendering solution in production for egghead.io. - Remove your usage of ENV["TRACE_REACT_ON_RAILS"] usage. You can get all tracing with either specifying
traceat your component or in your config/initializers/react_on_rails.rb file. - ReactOnRails::Utils.server_bundle_file_name and ReactOnRails::Utils.bundle_file_name were removed. React on Rails Pro contains upgrades to enable component and other types caching with React on Rails.
Upgrading to version 10
Pretty simple:
- Follow the steps to migrate to version 9 (except installing 10.x instead of 9.x)
- If you have
react_componentreturning hashes, then switch toreact_component_hashinstead
Upgrading to version 9
Why Webpacker?
Webpacker provides areas of value:
- View helpers that support bypassing the asset pipeline, which allows you to avoid double minification and enable source maps in production. This is 100% a best practice, as source maps in production greatly increases the value of services such as HoneyBadger or Sentry.
- A default Webpack config so that you only need to do minimal modifications and customizations. However, if you're doing server rendering, you may not want to give up control. Since Webpacker's default Webpack config is changing often, we at Shakacode can give you definitive advice on Webpack configuration best practices. In general, if you're happy with doing your own Webpack configuration, then we suggest using the
clientstrategy discussed below. Most corporate projects will prefer having more control than direct dependence on webpacker easily allows.
Integrating Webpacker
Reason for doing this: This enables your Webpack bundles to bypass the Rails asset pipeline and its extra minification, enabling you to use source-maps in production, while still maintaining total control over everything in the client directory.
From version 7 or lower
...while keeping your client directory
.gitignore: add/public/webpack/*.Gemfile: bumpreact_on_railsand addwebpacker.- layout views: anything bundled by Webpack will need to be requested by a
javascript_pack_tagorstylesheet_pack_tag. - Search your codebase for javascript_include_tag. Use the
config/initializers/assets.rb: we no longer need to modifyRails.application.config.assets.pathsor append anything toRails.application.config.assets.precompile.config/initializers/react_on_rails.rb:- Delete
config.generated_assets_dir. Webpacker's config now supplies this information. - Replace
config.npm_build_(test|production)_commandwithconfig.build_(test|production)_command.
- Delete
config/webpacker.yml: start with our example config (feel free to modify it as needed). I recommend setting dev_server.hmr to false however since HMR is currently broken.client/package.json: bumpreact_on_rails(I recommend bumpingwebpackas well). You'll also needjs-yamlif you're not already usingeslintandwebpack-manifest-pluginregardless.
Client Webpack config
- You'll need the following code to read data from the webpacker config:
const path = require('path');
const ManifestPlugin = require('webpack-manifest-plugin'); // we'll use this later
const webpackConfigLoader = require('react-on-rails/webpackConfigLoader');
const configPath = path.resolve('..', 'config');
const { output } = webpackConfigLoader(configPath);
- That output variable will be used for Webpack's
outputrules:
output: {
filename: '[name]-[chunkhash].js', // [chunkhash] because we've got to do our own cache-busting now
path: output.path,
publicPath: output.publicPath,
},
- ...as well as for the output of plugins like
webpack-manifest-plugin:
new ManifestPlugin({
publicPath: output.publicPath,
writeToFileEmit: true
}),
- If you're using referencing files or images with
url-loader&file-loader, their publicpaths will have to change as well:publicPath: '/webpack/', - If you're using
css-loader,webpack.optimize.CommonsChunkPlugin, orextract-text-webpack-plugin, they will also need cache-busting!
...and you're finally done!
...while replacing your client directory
- Make the same changes to
config/initializers/react_on_rails.rb as described above - Upgrade RoR & add Webpacker in the Gemfile
- Upgrade RoR in the
client/package.json - Run
bundle - Run
rails webpacker:install - Run
rails webpacker:install:react - Run
rails g react_on_rails:install - Move your entry point files to
app/javascript/packs - Either:
- Move all your source code to
app/javascript/bundles, move your linter configs to the root directory, and then delete theclientdirectory - or just delete the Webpack config and remove Webpack, its loaders, and plugins from your
client/package.json.
- Move all your source code to
...and you're done.
From version 8
For an example of upgrading, see react-webpack-rails-tutorial/pull/416.
-
Breaking Configuration Changes
- Added
config.node_modules_locationwhich defaults to""if Webpacker is installed. You may want to set this to'client'inconfig/initializers/react_on_rails.rbto keep yournode_modulesinside the/clientdirectory. - Renamed
- config.npm_build_test_command ==> config.build_test_command
- config.npm_build_production_command ==> config.build_production_command
- Added
-
Update the gemfile. Switch over to using the webpacker gem.
gem "webpacker"
-
Update for the renaming in the
WebpackConfigLoaderin your Webpack configuration. You will need to rename the following object properties:- webpackOutputPath ==> output.path
- webpackPublicOutputDir ==> output.publicPath
- hotReloadingUrl ==> output.publicPathWithHost
- hotReloadingHostname ==> settings.dev_server.host
- hotReloadingPort ==> settings.dev_server.port
- hmr ==> settings.dev_server.hmr
- manifest ==> Remove this one. We use the default for Webpack of manifest.json
- env ==> Use
const { env } = require('process'); - devBuild ==> Use
const devBuild = process.env.NODE_ENV !== 'production';
-
Edit your Webpack.config files:
-
Change your Webpack output to be like this. Be sure to have the hash or chunkhash in the filename, unless the bundle is server side.:
const webpackConfigLoader = require('react-on-rails/webpackConfigLoader');
const configPath = resolve('..', 'config');
const { output, settings } = webpackConfigLoader(configPath);
const hmr = settings.dev_server.hmr;
const devBuild = process.env.NODE_ENV !== 'production';
output: {
filename: isHMR ? '[name]-[hash].js' : '[name]-[chunkhash].js',
chunkFilename: '[name]-[chunkhash].chunk.js',
publicPath: output.publicPath,
path: output.path,
}, -
Change your ManifestPlugin definition to something like the following
new ManifestPlugin({
publicPath: output.publicPath,
writeToFileEmit: true
}),
-
-
Find your
webpacker_lite.ymland rename it towebpacker.yml- Consider copying a default webpacker.yml setup such as https://github.com/shakacode/react-on-rails-v9-rc-generator/blob/master/config/webpacker.yml
- If you are not using the webpacker Webpack setup, be sure to put in
compile: falsein thedefaultsection. - Alternately, if you are updating from webpacker_lite, you can manually change these:
- Add a default setting
cache_manifest: false - For production, set:
cache_manifest: true - Add a section like this under your development env:
Set hmr to your preference.
dev_server:
host: localhost
port: 3035
hmr: false - See the example
react_on_rails/spec/dummy/config/webpacker.yml. - Remove keys
hot_reloading_hostandhot_reloading_enabled_by_default. These are replaced by thedev_serverkey. - Rename
webpack_public_output_dirtopublic_output_path.
-
Edit your Procfile.dev
- Remove the env value WEBPACKER_DEV_SERVER as it's not used
- For hot loading:
- Set the
hmrkey in yourwebpacker.ymltotrue.
- Set the
Without integrating webpacker
- Bump your ReactOnRails versions in
Gemfile&package.json - In
/config/initializers/react_on_rails.rb:- Rename
config.npm_build_test_command==>config.build_test_command - Rename
config.npm_build_production_command==>config.build_production_command - Add
config.node_modules_location = "client"
- Rename
...and you're done.