Troubleshooting Guide
Having issues with React on Rails? This guide covers the most common problems and their solutions.
Summary for AI agents: Use this page for general React on Rails troubleshooting. For quick fixes to common setup issues, see Common Issues. For Pro-specific issues (streaming SSR, Node renderer, RSC), see Pro Troubleshooting. Always try bundle exec rails react_on_rails:doctor first.
π Quick Diagnosisβ
Is your issue with...?β
| Problem Area | Quick Check | Go to Section |
|---|---|---|
| Installation | Generator fails or components don't appear | Installation Issues |
| Compilation | Webpack errors, build failures | Build Issues |
| Runtime | Components not rendering, JavaScript errors | Runtime Issues |
| CSS Modules | Styles undefined, SSR CSS crashes | CSS Modules Issues |
| Styling (FOUC) | Unstyled content flash, layout jumps | Flash of Unstyled Content |
| Server Rendering | SSR not working, hydration mismatches | SSR Issues |
| Performance | Slow builds, large bundles, memory issues | Performance Issues |
π¨ Installation Issuesβ
"Generator fails with uncommitted changes"β
Error: You have uncommitted changes. Please commit or stash them.
Solution:
git add .
git commit -m "Add react_on_rails gem"
bin/rails generate react_on_rails:install
Why: The generator needs clean git state to show you exactly what it changed.
"Node or package manager not found"β
Error: Node.js not found, npm executable was not detected, or Yarn executable was not detected
Solution:
- Install Node.js 18+ from nodejs.org
- Use the bundled
npm, or install another package manager such aspnpm,yarn, orbun - On macOS, for example:
brew install node pnpm
"Package version is not an exact version" at bootβ
At boot, React on Rails verifies that the npm package version matches the gem version. It
resolves the installed version from your package manager's lockfile (yarn.lock β both Yarn
classic and Yarn Berry formats β package-lock.json, pnpm-lock.yaml, and bun.lock), so
semver ranges like ^17.0.0 in package.json are fine as long as a lockfile can answer. When no
lockfile can answer, the raw package.json version must be an exact version matching the gem.
Notes and known limitations:
- Binary
bun.lockbis not parsed. Migrate to bun's text lockfile withbun install --save-text-lockfile --frozen-lockfile --lockfile-only, then deletebun.lockbβ or pin the exact version in package.json. - A malformed or unreadable lockfile behaves like a missing one: resolution falls back to the package.json version.
- Multiple lockfiles: when lockfiles from several package managers exist,
yarn.lockis consulted first, thenpackage-lock.json, thenpnpm-lock.yaml, thenbun.lock. Delete lockfiles from managers you no longer use. - Monorepos / workspaces with a hoisted lockfile: the checker looks for the lockfile next to
the package.json it validates. Package managers hoist the lockfile to the workspace root, so a
Rails app that is a workspace member may resolve no lockfile and fall back to the strict
exact-version rule. Workarounds: pin the exact version in the app's package.json (the error
message shows the command), or set
REACT_ON_RAILS_SKIP_VALIDATION=trueif you accept the risk. - The check reads lockfiles, not
node_modules: if you pulled a change to the lockfile but have not re-run install, the check can pass while the app still runs old code fromnode_modules. If versions look right but runtime behavior disagrees, re-run your package manager's install.
π§ Build Issuesβ
"Module not found: Can't resolve 'react-on-rails'"β
Error in browser console or webpack output
Solution:
# Make sure the npm package is installed
pnpm add react-on-rails
# or: npm install react-on-rails --save
# or: yarn add react-on-rails
# If using local development with yalc
cd react_on_rails/
yalc publish
cd your_app/
yalc add react-on-rails
"Webpack compilation failed"β
Check these common causes:
- Syntax errors in your React components
- Missing dependencies in package.json
- Incorrect imports (check file paths and extensions)
Debug steps:
# Run webpack directly to see detailed errors
bin/webpack
# Or in development mode
bin/webpack --mode development
"ExecJS::RuntimeUnavailable"β
Error: JavaScript runtime not available
Solution:
# Add to your Gemfile
gem 'execjs'
gem 'mini_racer', platforms: :ruby
# Or use Node.js runtime
export EXECJS_RUNTIME=Node
β‘ Runtime Issuesβ
"Component not rendering"β
Symptoms: Empty div or no output where component should be
Check list:
-
Component registered?
import ReactOnRails from 'react-on-rails';
import MyComponent from './MyComponent';
ReactOnRails.register({ MyComponent }); -
Bundle included in view?
<%= javascript_pack_tag 'my-bundle' %>
<%= react_component('MyComponent') %> -
Component exported correctly?
// Use default export
export default MyComponent;
// Not named export for registration
"ReferenceError: window is not defined"β
Error during server-side rendering
Solution: Check your component for browser-only code:
// β Bad - will break SSR
const width = window.innerWidth;
// β
Good - check if window exists
const width = typeof window !== 'undefined' ? window.innerWidth : 1200;
// β
Better - use useEffect hook
useEffect(() => {
const width = window.innerWidth;
// Use width here
}, []);
"Props not updating"β
Symptoms: Component shows initial props but doesn't update
Common causes:
- Caching - Rails fragment caching may cache React components
- Turbo/Turbolinks - Page navigation isn't re-initializing React
- Development mode - Hot reloading not working
Solutions:
<!-- Disable caching for development -->
<% unless Rails.env.development? %>
<% cache do %>
<%= react_component('MyComponent', props: @props) %>
<% end %>
<% else %>
<%= react_component('MyComponent', props: @props) %>
<% end %>
"Flash of Unstyled Content (FOUC)"β
There are two common causes of FOUC in React on Rails applications:
Type 1: SSR with auto_load_bundleβ
Symptoms: Page briefly shows unstyled content before CSS loads, particularly with SSR and auto_load_bundle
Root Cause: When using auto_load_bundle = true with server-side rendering, react_component calls trigger append_stylesheet_pack_tag during body rendering, but these appends must execute BEFORE the stylesheet_pack_tag in the <head>.
Solution: Use the content_for :body_content pattern to ensure appends happen before the head renders.
See: FOUC Prevention Guide for detailed solutions and examples.
Quick fix:
<% content_for :body_content do %>
<%= react_component "MyComponent", prerender: true %>
<% end %>
<!DOCTYPE html>
<html>
<head>
<%= stylesheet_pack_tag(media: 'all') %>
</head>
<body>
<%= yield :body_content %>
</body>
</html>
Type 2: Tailwind/Utility-First CSS Frameworksβ
Symptoms: Layout appears broken or jumps on initial page loadβsidebars collapse, flex containers stack vertically, backgrounds are white instead of colored.
Root Cause: When using Tailwind CSS (or similar utility-first frameworks), your layout HTML contains CSS classes like flex, h-screen, bg-slate-100 that have no effect until the CSS bundle loads. The browser renders the raw HTML structure without any styling.
Example of problematic layout:
<!-- These classes do nothing until Tailwind CSS loads -->
<div class="flex flex-row h-screen w-screen">
<div class="flex flex-col bg-slate-100 min-w-[400px]">
<!-- sidebar -->
</div>
<div class="flex-1 overflow-y-auto">
<!-- main content -->
</div>
</div>
Solution: Inline critical CSS for layout-affecting properties in the <head> before your main stylesheet loads. Use stable semantic selectors (not Tailwind utility class names) so the critical CSS doesn't drift when you add or remove utility classes.
Step 1: Add semantic classes to your layout's structural elements (alongside existing Tailwind classes):
<body class="app-body bg-white">
<div class="app-shell flex flex-row h-screen w-screen">
<div class="app-sidebar flex flex-col overflow-y-auto p-5 bg-slate-100 ...">
<!-- sidebar content -->
</div>
<div class="app-main flex-1 overflow-x-hidden overflow-y-auto">
<div class="app-main-content p-5">
<!-- main content -->
</div>
</div>
</div>
Step 2: Create a critical styles partial (e.g., app/views/layouts/_critical_styles.html.erb) targeting those semantic selectors:
<%#
Critical CSS for preventing FOUC. Uses semantic selectors so it doesn't
need to change when Tailwind utility classes are added or removed.
Only update when the fundamental layout structure changes.
%>
<style data-critical-styles>
.app-body { background-color: #fff; }
.app-shell { display: flex; flex-direction: row; height: 100vh; width: 100vw; }
.app-sidebar {
display: flex; flex-direction: column; overflow-y: auto; padding: 1.25rem;
background-color: #f1f5f9; border-style: solid;
border-right-width: 2px; border-color: #334155;
min-width: 400px; max-width: 400px;
}
.app-main { flex: 1 1 0%; overflow-x: hidden; overflow-y: auto; }
.app-main-content { padding: 1.25rem; }
</style>
Step 3: Include it in your layout's <head> before the stylesheet:
<head>
<%= render "layouts/critical_styles" %>
<%= stylesheet_pack_tag('application', media: 'all') %>
</head>
Guidelines for critical CSS:
- Use semantic selectors -
.app-shell,.app-sidebar, etc. instead of mirroring Tailwind class names - Keep it minimal - Only define the layout shell (not component styles)
- Focus on layout-affecting properties -
display,flex,width,height,position - Include visible defaults - Background colors and borders that prevent jarring changes
- Add
data-critical-styles- Makes it easy to test that critical styles appear before the bundle
π¨ CSS Modules Issuesβ
"CSS modules returning undefined" (Shakapacker 9+)β
Symptoms:
import css from './Component.module.scss'returnsundefined- SSR crashes:
Cannot read properties of undefined (reading 'className') - Build warning:
export 'default' (imported as 'css') was not found
Root Cause: Shakapacker 9 changed the default CSS Modules configuration from default exports to named exports (namedExport: true).
Solution: Configure CSS loader to use default exports:
// config/webpack/commonWebpackConfig.js
const { generateWebpackConfig } = require('shakapacker');
const commonWebpackConfig = () => {
const baseWebpackConfig = generateWebpackConfig();
baseWebpackConfig.module.rules.forEach((rule) => {
if (rule.use && Array.isArray(rule.use)) {
const cssLoader = rule.use.find((loader) => {
const loaderName = typeof loader === 'string' ? loader : loader?.loader;
return loaderName?.includes('css-loader');
});
if (cssLoader?.options?.modules) {
cssLoader.options.modules.namedExport = false;
cssLoader.options.modules.exportLocalsConvention = 'camelCase';
}
}
});
return baseWebpackConfig;
};
See: Rspack Migration Guide for complete configuration details.
"CSS modules work in dev but fail in SSR"β
Cause: Server-side config overwrites CSS modules settings instead of merging them.
Solution: Preserve existing CSS modules configuration:
// β Wrong
cssLoader.options.modules = { exportOnlyLocals: true };
// β
Correct
cssLoader.options.modules = {
...cssLoader.options.modules,
exportOnlyLocals: true,
};
"Intermittent CSS failures with Rspack"β
Cause: CSS extraction not properly filtered from server bundle. Rspack uses different loader paths than Webpack.
Solution: Filter both Webpack and Rspack CSS extract loaders:
rule.use = rule.use.filter((item) => {
const testValue = typeof item === 'string' ? item : item?.loader;
return !(
testValue?.match(/mini-css-extract-plugin/) ||
testValue?.includes('cssExtractLoader') || // Rspack path
testValue === 'style-loader'
);
});
π₯οΈ Server-Side Rendering Issuesβ
"Server rendering not working"β
Check:
-
Prerender enabled?
<%= react_component('MyComponent', props: @props, prerender: true) %> -
JavaScript runtime available?
# Add to Gemfile if missing
gem 'mini_racer' -
No browser-only code in component? (see "window is not defined" above)
"Hydration mismatch warnings"β
Symptoms: React warnings about server/client content differences
Common causes:
- Different props between server and client render
- Browser-only code affecting initial render
- Date/time differences between server and client
Debug:
// Add this to see what props are being used
console.log('Server props:', props);
console.log('Client render time:', new Date());
π Performance Issuesβ
"Slow webpack builds"β
Solutions:
-
Enable caching:
# config/shakapacker.yml
development:
cache_manifest: true -
Use webpack-dev-server:
./bin/dev # Uses Procfile.dev with webpack-dev-server -
Check for large dependencies:
yarn why package-name
webpack-bundle-analyzer public/packs/manifest.json
"Large bundle sizes"β
Solutions:
-
Code splitting:
// Use dynamic imports
const MyComponent = lazy(() => import('./MyComponent')); -
Check bundle analysis:
ANALYZE=true bin/webpack -
Remove unused dependencies:
yarn remove unused-package
π οΈ Advanced Debuggingβ
For a comprehensive debugging guide covering SSR issues, hydration mismatches, and log analysis, see the Debugging Guide.
Enable verbose loggingβ
# config/initializers/react_on_rails.rb
ReactOnRails.configure do |config|
config.logging_on_server = true
end
The open-source gem always renders on the server with ExecJS; there is no configuration option to select a different server render method. For a standalone Node rendering process, use React on Rails Pro's Node renderer, which is configured via ReactOnRailsPro.configure rather than through the open-source configuration.
Debug webpack configurationβ
# See the final webpack config
bin/webpack --config-dump
Check component registrationβ
// In browser console
console.log(ReactOnRails.registeredComponents());
π Still Stuck?β
Before asking for help, gather this infoβ
- React on Rails version (
bundle list react_on_rails) - Rails version (
rails -v) - Ruby version (
ruby -v) - Node version (
node -v) - Error messages (full stack trace)
- Relevant code snippets
Get community helpβ
- GitHub Issues - Bug reports and feature requests
- GitHub Discussions - Questions and help
- React + Rails Slack - Real-time community support
Professional supportβ
- ShakaCode offers consulting and support services
- React on Rails Pro includes priority support
π‘ Tip: Most issues are solved by ensuring your setup matches the Quick Start Guide exactly.