This documentation shows how to create a React on Rails application using React on Rails v15 auto-registration with the corrected installation sequence that fixes the "package.json not found" error.
📍 Working Demo: https://github.com/shakacode/test-react-on-rails-v15-hello-world-on-rails-v8
This sample app demonstrates:
- ✅ Auto-registration feature: Zero manual
ReactOnRails.register()calls needed - ✅ Bundle splitting: Lightweight (~50KB) vs Heavy (~2.7MB) components
- ✅ File-system-based bundle generation: Components auto-detected from directory structure
- ✅ Correct installation order: Shakapacker first, then React on Rails
- ✅ Rails 8.0.1 with React 19.1.1 and React on Rails 15.0.0
- ✅ Shakapacker 8.3.0 with
nested_entries: true - ✅ Server-side rendering (SSR) with prerender: true
- ✅ Modern React patterns with hooks, CSS modules, and dynamic imports
- ✅ Git commit history documenting each step
# 1. Create new Rails 8 app
rails new my_app --skip-jbuilder
cd my_app
# 2. Add Shakapacker FIRST (this creates package.json)
bundle add shakapacker
bundle exec rails generate shakapacker:install
# 3. Then add React on Rails (now package.json exists)
bundle add react-on-rails
bundle exec rails generate react_on_rails:install
# 4. Install dependencies with npm
npm install
# 5. Enable auto-registration in config/initializers/react_on_rails.rb
# Add these lines:
# config.components_subdirectory = "ror_components"
# config.auto_load_bundle = true
# config.server_bundle_js_file = "server-bundle.js"
# 6. Create components using directory structure (no generators needed!)
mkdir -p app/javascript/src/HelloWorld/ror_components
mkdir -p app/javascript/src/HeavyMarkdownEditor/ror_components
# 7. Generate webpack entries for auto-registration
rake react_on_rails:generate_packs
# 8. Run development server
bin/dev- Shakapacker creates
package.jsonand sets up the JavaScript build pipeline - React on Rails generator expects
package.jsonto exist when adding React dependencies - Wrong order causes:
ERROR: package.json not found
React on Rails v15 auto-registration uses a file-system-based approach where components are automatically detected and registered:
app/
├── controllers/
│ ├── hello_world_controller.rb
│ └── heavy_markdown_editor_controller.rb
│ └── heavy_markdown_editor_content.md # Content file (simulates database)
├── javascript/
│ ├── src/ # Auto-registration source directory
│ │ ├── HelloWorld/ # Component name (directory)
│ │ │ └── ror_components/ # Magic directory for auto-detection
│ │ │ ├── HelloWorld.jsx # Component implementation
│ │ │ └── HelloWorld.module.css # CSS modules styling
│ │ └── HeavyMarkdownEditor/ # Second component
│ │ └── ror_components/
│ │ ├── HeavyMarkdownEditor.jsx
│ │ └── HeavyMarkdownEditor.module.css
│ ├── generated/ # Auto-generated webpack entries
│ │ ├── HelloWorld.js # Generated by rake task
│ │ ├── HeavyMarkdownEditor.js # Generated by rake task
│ │ └── server-bundle-generated.js # Generated server bundle
│ └── packs/
│ ├── application.js # Main application entry
│ └── server-bundle.js # Server rendering entry
└── views/
├── hello_world/
│ └── index.html.erb
├── heavy_markdown_editor/
│ └── index.html.erb
└── layouts/
└── application.html.erb # Unified layout (no component-specific)
- Magic Directory:
app/javascript/src/ComponentName/ror_components/ - Auto-Detection:
rake react_on_rails:generate_packsscans these directories - Generated Entries: Webpack entries created in
app/javascript/generated/ - No Manual Registration: Components automatically available to Rails views
- Bundle Splitting: Each component gets its own webpack bundle
This app demonstrates React on Rails' auto-registration feature with bundle splitting:
- Lightweight bundle (~50KB) with React basics
- Interactive name input with React state
- Navigation link to HeavyMarkdownEditor
- CSS modules for styling
- Server-side rendering enabled
- Demonstrates fast-loading components
- Heavy bundle (~2.7MB) with 58+ dependencies including:
- ReactMarkdown for parsing
- Remark GFM for GitHub-flavored markdown
- Dynamic imports to prevent SSR issues
- Live markdown editor with preview
- Skeleton loader to prevent FOUC
- Content loaded from Rails controller (simulating database)
- Demonstrates bundle splitting benefits
React on Rails v15 automatically generates webpack entries. No manual registration needed!
generated/HelloWorld.js (auto-generated):
import ReactOnRails from 'react-on-rails';
import * as HelloWorldModule from '../src/HelloWorld/ror_components/HelloWorld.module.css';
import HelloWorld from '../src/HelloWorld/ror_components/HelloWorld';
ReactOnRails.register({
HelloWorld,
});generated/HeavyMarkdownEditor.js (auto-generated):
import ReactOnRails from 'react-on-rails';
import * as HeavyMarkdownEditorModule from '../src/HeavyMarkdownEditor/ror_components/HeavyMarkdownEditor.module.css';
import HeavyMarkdownEditor from '../src/HeavyMarkdownEditor/ror_components/HeavyMarkdownEditor';
ReactOnRails.register({
HeavyMarkdownEditor,
});rake react_on_rails:generate_packs. Don't edit them manually!
class HeavyMarkdownEditorController < ApplicationController
def index
@heavy_markdown_editor_props = {
initialText: load_demo_content, # Loads from .md file (simulates database)
title: "React on Rails Demo Article",
author: "Demo System",
lastModified: Time.current
}
end
private
def load_demo_content
# In production: Article.find(id).content
content_file = File.join(__dir__, 'heavy_markdown_editor_content.md')
File.read(content_file)
rescue Errno::ENOENT => e
"# Demo content not available"
end
end<%= react_component("HeavyMarkdownEditor",
props: @heavy_markdown_editor_props,
prerender: true) %><!DOCTYPE html>
<html>
<head>
<title>React on Rails v15 Auto-Registration</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
<%= javascript_importmap_tags %>
</head>
<body>
<%= yield %>
</body>
</html>Key Change: With auto-registration, the layout doesn't need component-specific javascript_pack_tag calls. The generated bundles are automatically loaded when components are rendered.
Rails.application.routes.draw do
root "hello_world#index"
get 'hello_world', to: 'hello_world#index'
get 'second_component', to: 'second_component#index'
endimport React, { useState } from 'react';
import styles from './HelloWorld.module.css';
const HelloWorld = (props) => {
const [name, setName] = useState(props.name);
return (
<div>
<h3>Hello, {name}!</h3>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
/>
</div>
);
};
export default HelloWorld;/* HelloWorld.module.css */
.bright {
color: red;
font-weight: bold;
}- Proper Installation Sequence: Shakapacker → React on Rails
- Component Auto-Registration: No manual ReactOnRails.register calls needed
- Bundle Separation: Each component has its own JavaScript pack
- Modern React: Hooks, functional components, CSS modules
- Rails Integration: Controllers, views, layouts working together
- Navigation: Component-to-component routing via Rails routes
# Development server with Hot Module Replacement
bin/dev
# Access the application
open http://localhost:3000We've enhanced the bin/dev script to support three different modes:
Development Mode (default - with HMR and FOUC):
bin/dev
open http://localhost:3000Static Development Mode (no HMR, no FOUC):
# Development environment with extracted CSS - best of both worlds
bin/dev static
open http://localhost:3000Production-Assets Mode (optimized bundles, no FOUC):
# Enhanced script automatically precompiles and serves production assets
bin/dev prod
open http://localhost:3001
# Or use the full command name
bin/dev production-assetsHelp:
bin/dev help| Aspect | Development (HMR) | Static Development | Production-Assets |
|---|---|---|---|
| FOUC | ❌ May occur during HMR | ✅ No FOUC | ✅ No FOUC |
| HMR | ✅ Live reloading | ❌ Manual refresh | ❌ Manual refresh |
| CSS Loading | CSS modules async | Extracted files | Extracted + minified |
| Bundle Size | Full + source maps | Full + source maps | Minified + optimized |
| Build Speed | Fastest | Fast | Slowest |
| Environment | Development | Development | Production |
| Port | 3000 | 3000 | 3001 |
| Use Case | Active development | Testing without FOUC | Pre-deploy testing |
💡 Recommendation: Use bin/dev static when you need to test the UI without FOUC but still want development speed and source maps.
# Remove production assets to return to development mode
rm -rf public/packs
# Restart development server
bin/devThis repository's commits document each step:
- Initial commit: Rails 8 + React on Rails 15.0 + Shakapacker 8.3 hello world app
- Switch to npm: Remove yarn lockfile, use npm as default
- Root redirect: Add root path redirect to hello_world
- Second component: Create SecondComponent to demonstrate auto-registration
- Navigation: Add navigation link between components
You can verify the installation sequence works by:
- Following this exact sequence on a fresh Rails 8 app
- Confirming no "package.json not found" errors occur
- Running the app and seeing both components work
- Checking that auto-registration loads components properly
- Cause: Installing React on Rails before Shakapacker
- Solution: Install Shakapacker first to create package.json
- Check: Ensure bundle is included in layout via
javascript_pack_tag - Check: Component is registered in the corresponding pack file
- Check: Routes are configured correctly
- Check: package-lock.json exists (not yarn.lock)
- Check: All dependencies are installed via
npm install - Check: Babel configuration is present in package.json
To recreate this app, an AI agent should:
- Follow the exact installation sequence documented here
- Use npm as the default package manager
- Create separate bundles for each component to demonstrate isolation
- Include navigation between components to show routing
- Add proper styling to make the demo visually appealing
- Document each step with meaningful git commits
- Test the final result to ensure everything works
This approach creates a robust sample that demonstrates both the technical fix and best practices for React on Rails development.