docs/oss/upgrading/upgrading-react-on-rails.md
[!NOTE] Summary for AI agents: Use this page when the user is upgrading between React on Rails versions (e.g., v13 → v14). For upgrading from OSS to Pro, see Upgrade to Pro. For migrating from the
react-railsgem, see Migrating from react-rails. Check the release notes for version-specific breaking changes.
If you would like help in migrating between React on Rails versions or help with implementing server rendering, please contact [email protected] 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.
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)shakapacker.yml settingsBefore changing versions, check these first:
react_on_rails requires Rails 5.2+. Rails 5.1 apps need a Rails upgrade before they can bundle v16.webpacker, upgrade to shakapacker first.^, ~, 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
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.
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'
SHAKAPACKER_SKIP_PRECOMPILE_HOOK exists.In CI, run precompile preparation explicitly once before webpack compilation or test startup, rather than relying on hook timing in watch-like flows.
This release includes major generator improvements, development workflow enhancements, and Pro stability fixes. See the v16.4.0 Release Notes for full details.
Key actions required:
config/react_on_rails_pro_license.key is no longer read. Move the token to the REACT_ON_RAILS_PRO_LICENSE environment variablecustom_rsc_payload_template overrides — the template is now rendered with formats: [:text]. If your override is .html.erb, rename it to .text.erbThis 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.3.0 Release Notes for details.
Key changes:
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:
config.immediate_hydration from your initializer - this config option has been removed (automatic for Pro users)package.json - semver wildcards (^, ~, *) now cause boot failuresreact-on-rails, switch to react-on-rails-proReactOnRails.configure to ReactOnRailsPro.configureThis 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.
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
webpacker, finish that migration firstshakapacker.yml settingsbin/dev if neededTest 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
Symptoms: Webpack cannot find modules referenced in your configuration
Solutions:
rm -rf node_modules/.cacheFor troubleshooting build errors, see the build errors guide.
react_on_rails:generate_packs task with detailed debugging guidancePreviously, 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:
rails/webpacker that you had.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.
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.
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.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.
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(),
});
export default (props, railsContext) => () => <Component {...{ ...props, railsContext }} />;
domNodeId, to call ReactDOM.hydrate. If the function takes 3 params, there is no migration needed for that function.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:
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:
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.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
server_render_method from 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 [email protected]. We have a custom node rendering solution in production for egghead.io.trace at your component or in your config/initializers/react_on_rails.rb file.Pretty simple:
react_component returning hashes, then switch to react_component_hash insteadWebpacker provides areas of value:
client strategy discussed below. Most corporate projects will prefer having more control than direct dependence on webpacker easily allows.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.
client directory.gitignore: add /public/webpack/*.Gemfile: bump react_on_rails and add webpacker.javascript_pack_tag or stylesheet_pack_tag.config/initializers/assets.rb: we no longer need to modify Rails.application.config.assets.paths or append anything to Rails.application.config.assets.precompile.config/initializers/react_on_rails.rb:
config.generated_assets_dir. Webpacker's config now supplies this information.config.npm_build_(test|production)_command with config.build_(test|production)_command.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: bump react_on_rails (I recommend bumping webpack as well). You'll also need js-yaml if you're not already using eslint and webpack-manifest-plugin regardless.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);
output rules: output: {
filename: '[name]-[chunkhash].js', // [chunkhash] because we've got to do our own cache-busting now
path: output.path,
publicPath: output.publicPath,
},
webpack-manifest-plugin:
new ManifestPlugin({
publicPath: output.publicPath,
writeToFileEmit: true
}),
url-loader & file-loader, their publicpaths will have to change as well: publicPath: '/webpack/',css-loader, webpack.optimize.CommonsChunkPlugin, or extract-text-webpack-plugin, they will also need cache-busting!...and you're finally done!
client directoryconfig/initializers/react_on_rails.rb as described aboveclient/package.jsonbundlerails webpacker:installrails webpacker:install:reactrails g react_on_rails:installapp/javascript/packsapp/javascript/bundles, move your linter configs to the root directory, and then delete the client directoryclient/package.json....and you're done.
For an example of upgrading, see react-webpack-rails-tutorial/pull/416.
Breaking Configuration Changes
config.node_modules_location which defaults to "" if Webpacker is installed. You may want to set this to 'client' in config/initializers/react_on_rails.rb to keep your node_modules inside the /client directory.Update the gemfile. Switch over to using the webpacker gem.
gem "webpacker"
Update for the renaming in the WebpackConfigLoader in your Webpack configuration.
You will need to rename the following object properties:
const { env } = require('process');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.yml and rename it to webpacker.yml
compile: false in the default section.cache_manifest: false
cache_manifest: true
dev_server:
host: localhost
port: 3035
hmr: false
react_on_rails/spec/dummy/config/webpacker.yml.hot_reloading_host and hot_reloading_enabled_by_default. These are replaced by the dev_server key.webpack_public_output_dir to public_output_path.Edit your Procfile.dev
hmr key in your webpacker.yml to true.Gemfile & package.json/config/initializers/react_on_rails.rb:
config.npm_build_test_command ==> config.build_test_commandconfig.npm_build_production_command ==> config.build_production_commandconfig.node_modules_location = "client"...and you're done.