docs/oss/migrating/migrating-from-vite-rails.md
vite_railsThis guide is for Rails apps that currently use vite_rails with React and want to move to React on Rails.
React on Rails is a better fit when you want one or more of these:
react_componentIf your app is already happy with a Vite-only client-rendered setup, this migration is optional.
Before you start, make sure the current app still installs cleanly on the Ruby and Node versions you plan to use for the migration.
bundle install fails on older native gems such as pg, nio4r, or msgpack, refresh those gems or use the Ruby version already pinned by the app before introducing React on Rails.Then inventory the Vite-specific pieces in your app:
vite_client_tag, vite_react_refresh_tag, vite_stylesheet_tag, vite_typescript_tag, and vite_asset_pathapp/frontend/entrypoints/*vite.config.tsconfig/vite.rb and config/vite.jsonbin/vite, bin/live, or a Procfile.dev entry that runs Viteimport.meta.envExpect this migration to touch both Ruby and JavaScript entrypoints.
Do the migration in a branch and keep the Vite setup working until the new React on Rails path is rendering the same screens.
For anything beyond a tiny app, prefer a route-by-route cutover instead of a big-bang rewrite.
bundle add shakapacker --strict
bundle add react_on_rails --strict
bundle exec rails generate react_on_rails:install
The generator adds the React on Rails initializer, bin/dev, Shakapacker config, example routes, and the server bundle entrypoint.
A typical Vite layout looks like this:
<%= vite_client_tag %>
<%= vite_react_refresh_tag %>
<%= vite_stylesheet_tag "styles.scss" %>
<%= vite_typescript_tag "application" %>
React on Rails + Shakapacker layouts use pack tags instead:
<%= stylesheet_pack_tag %>
<%= javascript_pack_tag %>
These empty pack tags are the default for React on Rails auto-bundling — React on Rails injects component-specific bundles per page. If you use a manual entrypoint instead (non-auto-bundling), pass the pack name explicitly, e.g. javascript_pack_tag "application".
A common Vite layout is:
app/frontend/
components/
entrypoints/
styles/
For React on Rails, move the code into app/javascript/. A good target is:
app/javascript/
packs/
src/
For auto-bundling, move page-level components into a ror_components directory, for example:
app/javascript/src/Hero/ror_components/Hero.client.jsx
Vite apps often mount React manually from an entrypoint:
createRoot(document.getElementById('hero')).render(<Hero />);
With React on Rails, render the component from the Rails view instead:
<%= react_component("Hero", props: { title: "Welcome" }, auto_load_bundle: true) %>
This is the key mental model shift: Rails decides where the component mounts, and React on Rails handles registration and hydration.
vite_asset_pathIf your ERB templates use vite_asset_path, convert those assets to one of these patterns:
import.meta.envVite-specific import.meta.env usage needs to be replaced. In a React on Rails app, prefer:
railsContext for request-aware valuesprocess.env in server-rendered bundles (available natively in Node); for client bundles, values must be injected via webpack's DefinePlugin or EnvironmentPluginVite apps usually have a dev command like:
vite: bin/vite dev
web: bin/rails s
React on Rails uses the generated bin/dev flow with Rails plus the Shakapacker watcher.
After migration:
bin/rails db:prepare
bin/dev
After the new React on Rails entrypoints are working, remove:
vite_rails and related Vite gemsvite.config.tsconfig/vite.rbconfig/vite.jsonbin/vite and Vite-only Procfile entriesapp/frontend/entrypoints/*Do this only after the Rails views are using React on Rails helpers and the app no longer depends on Vite-specific helpers.
For a Vite app with:
app/frontend/components/Hero.jsxapp/frontend/entrypoints/application.ts<div id="hero"></div> in ERBone reasonable React on Rails target is:
app/javascript/src/Hero/ror_components/Hero.client.jsxapp/views/... uses <%= react_component("Hero", ..., auto_load_bundle: true) %>server-bundle.js remains available if you later add SSRThe migration is mostly about asset/build integration, mounting strategy, and optional SSR capability.