Skip to content

Commit b6178b8

Browse files
justin808claude
andcommitted
Add React on Rails Pro requirement for TanStack Router SSR
TanStack Router requires Node.js APIs (setTimeout, clearTimeout, etc.) that are not available in the default ExecJS environment. This commit adds React on Rails Pro with the Node Renderer as a required dependency. Key changes: - Add react_on_rails_pro gem (required, not optional) - Add react-on-rails-pro/ directory with Node Renderer configuration - Add config/initializers/react_on_rails_pro.rb with Node Renderer setup - Add comprehensive setup documentation in docs/react-on-rails-pro-setup.md - Update Procfile.dev to include Node Renderer process - Remove ExecJS polyfills - no fallback, Pro is required - Add .env.example for environment variable configuration Setup instructions: 1. Get a free license: Contact team@shakacode.com 2. Configure bundler for GitHub Packages 3. Run bundle install 4. Set up Node Renderer: cd react-on-rails-pro && npm install 5. Start with bin/dev Related issues: - Documentation: shakacode/react_on_rails#2299 - TanStack Router: shakacode/react_on_rails#2298 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent da9adf0 commit b6178b8

18 files changed

Lines changed: 618 additions & 74 deletions

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# React on Rails Pro License Token
2+
# Get a free license: Contact team@shakacode.com or justin@shakacode.com
3+
# Create a GitHub PAT with 'read:packages' scope
4+
REACT_ON_RAILS_PRO_TOKEN=ghp_your_github_token_here
5+
6+
# Node Renderer Configuration (optional)
7+
# RENDERER_URL=http://localhost:3800
8+
# RENDERER_PASSWORD=tanstack-demo-renderer
9+
# RENDERER_PORT=3800
10+
# RENDERER_LOG_LEVEL=debug
11+
# NODE_RENDERER_CONCURRENCY=3

demos/basic-v16-rspack-tanstack/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ playwright-report/
2828
.env*
2929
!.env.example
3030

31+
# React on Rails Pro (contains auth tokens)
32+
react-on-rails-pro/.npmrc
33+
react-on-rails-pro/node_modules/
34+
3135
# Ignore master key
3236
config/master.key
3337

demos/basic-v16-rspack-tanstack/Gemfile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@ gem "shakapacker", "~> 9.3.0"
2626
# React integration for Rails with SSR support
2727
gem "react_on_rails", "16.2.0.rc1"
2828

29+
# React on Rails Pro - Node Renderer for production-grade SSR (REQUIRED)
30+
# TanStack Router requires Node.js APIs (setTimeout, clearTimeout, etc.)
31+
# that are not available in the default ExecJS environment.
32+
#
33+
# Setup instructions: See docs/react-on-rails-pro-setup.md
34+
# Free license: Contact team@shakacode.com or justin@shakacode.com
35+
source "https://rubygems.pkg.github.com/shakacode-tools" do
36+
gem "react_on_rails_pro", "~> 4.0"
37+
end
38+
2939
# Colored terminal output for build scripts
3040
gem "rainbow"
3141

demos/basic-v16-rspack-tanstack/Procfile.dev

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@
33
rails: bundle exec rails s -p 3000
44
wp-client: WEBPACK_SERVE=true bin/shakapacker-dev-server
55
wp-server: SERVER_BUNDLE_ONLY=yes bin/shakapacker --watch
6+
# React on Rails Pro Node Renderer (REQUIRED for SSR)
7+
# TanStack Router requires Node.js APIs not available in ExecJS
8+
# Setup: See docs/react-on-rails-pro-setup.md
9+
node-renderer: cd react-on-rails-pro && npm run node-renderer

demos/basic-v16-rspack-tanstack/README.md

Lines changed: 64 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
A React on Rails demo application showcasing **TanStack Router** integration with **Rspack** and **server-side rendering (SSR)**.
44

5+
> **⚠️ React on Rails Pro Required**: This demo requires [React on Rails Pro](https://www.shakacode.com/react-on-rails-pro/) with its **Node server renderer**. TanStack Router uses Node.js APIs (`setTimeout`, `clearTimeout`, etc.) that are not available in the default ExecJS environment. The Node renderer provides a proper Node.js runtime for SSR.
6+
57
## Gem Versions
68

79
This demo uses:
@@ -105,14 +107,39 @@ The following directories contain generated files and are **gitignored**:
105107

106108
### SSR Configuration
107109

108-
The SSR bundle output path is configured in `config/shakapacker.yml`:
110+
This demo requires **React on Rails Pro's Node server renderer** because TanStack Router uses Node.js APIs (`setTimeout`, `clearTimeout`, etc.) internally for route loading. These APIs are not available in the default ExecJS environment used by the open-source React on Rails gem.
109111

110-
```yaml
111-
default:
112-
private_output_path: ssr-generated
113-
```
112+
#### Why Node Renderer?
113+
114+
The default ExecJS-based SSR in React on Rails runs JavaScript in a limited environment (typically via MiniRacer or ExecJS) that lacks:
115+
116+
- Timer functions (`setTimeout`, `clearTimeout`, `setInterval`, `clearInterval`)
117+
- Full `console` API
118+
- Other Node.js built-in modules
119+
120+
TanStack Router's internal implementation relies on these APIs, making the Node renderer essential.
121+
122+
#### Configuration
114123

115-
The server bundle configuration in `config/webpack/serverWebpackConfig.js` automatically uses this path.
124+
1. **Configure React on Rails Pro** in `config/initializers/react_on_rails.rb`:
125+
126+
```ruby
127+
ReactOnRails.configure do |config|
128+
config.server_render_method = "NodeJS"
129+
# ... other configuration
130+
end
131+
```
132+
133+
2. **Webpack/Rspack target**: The server bundle is configured with `target: 'node'` in `config/webpack/serverWebpackConfig.js`
134+
135+
3. **Output path**: Configured in `config/shakapacker.yml`:
136+
137+
```yaml
138+
default:
139+
private_output_path: ssr-generated
140+
```
141+
142+
For more information on React on Rails Pro and the Node renderer, see the [React on Rails Pro documentation](https://www.shakacode.com/react-on-rails-pro/).
116143
117144
## Scripts
118145
@@ -144,16 +171,44 @@ npm run test:headed
144171
SKIP_WEB_SERVER=true npm test
145172
```
146173

147-
## SSR Limitations
174+
## Limitations
175+
176+
### Route Synchronization
148177

149-
This demo uses synchronous SSR compatible with React on Rails. **Async route loaders are not supported** in this configuration. The `router.load()` call happens synchronously.
178+
Each TanStack Router route that requires SSR must have a corresponding Rails route defined in `config/routes.rb`. This ensures the Rails server can handle direct URL requests and render the correct initial HTML.
179+
180+
```ruby
181+
# config/routes.rb
182+
get "about", to: "tanstack_app#index"
183+
get "users/:userId", to: "tanstack_app#index"
184+
# ... add routes for each TanStack route
185+
```
186+
187+
For production apps with many routes, consider a catch-all route (with API route exclusions):
188+
189+
```ruby
190+
get '*path', to: 'tanstack_app#index', constraints: ->(req) { !req.path.start_with?('/api') }
191+
```
192+
193+
### React on Rails Pro Required
194+
195+
This demo **requires React on Rails Pro** with the Node server renderer. The open-source ExecJS-based renderer cannot run TanStack Router due to missing Node.js APIs.
196+
197+
### Synchronous SSR Only
198+
199+
This demo uses synchronous SSR. **Async route loaders are not supported** in this configuration. The `router.load()` call happens synchronously.
150200

151201
For async data fetching, consider:
152202

153203
1. Passing data as props from the Rails controller
154-
2. Using React on Rails' `renderFunction` pattern for async support
204+
2. Using React on Rails Pro's streaming SSR for async support
155205
3. Using client-side data fetching with loading states
156206

207+
### Development-Only Features
208+
209+
- **TanStack Router DevTools** are only available in development mode
210+
- **React Fast Refresh** (HMR) is only active when running `bin/dev`
211+
157212
## Deployment
158213

159214
For deployment, ensure you run the asset precompilation step:
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1+
// import statement added by react_on_rails:generate_packs rake task
2+
import './../generated/server-bundle-generated.js';
13
// Placeholder comment - auto-generated imports will be prepended here by react_on_rails:generate_packs

demos/basic-v16-rspack-tanstack/app/javascript/src/routes/__root.tsx

Lines changed: 121 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,133 @@
1-
import { createRootRoute, Link, Outlet } from '@tanstack/react-router';
1+
import { createRootRoute, ErrorComponent, Link, Outlet } from '@tanstack/react-router';
22
import * as React from 'react';
33

44
export const Route = createRootRoute({
55
component: RootComponent,
6+
errorComponent: RootErrorComponent,
7+
notFoundComponent: NotFoundComponent,
68
});
79

8-
function RootComponent() {
10+
/**
11+
* Error boundary component for the root route.
12+
* Catches and displays errors that occur in child routes.
13+
*/
14+
function RootErrorComponent({ error }: { error: Error }) {
15+
return (
16+
<div className="app">
17+
<RootNav />
18+
<main style={{ padding: '1rem' }}>
19+
<div
20+
style={{
21+
padding: '1rem',
22+
backgroundColor: '#ffebee',
23+
borderRadius: '4px',
24+
border: '1px solid #ef5350',
25+
}}
26+
>
27+
<h1>Something went wrong</h1>
28+
<ErrorComponent error={error} />
29+
</div>
30+
</main>
31+
</div>
32+
);
33+
}
34+
35+
/**
36+
* Not found component for unmatched routes.
37+
*/
38+
function NotFoundComponent() {
939
return (
1040
<div className="app">
11-
<nav
12-
style={{
13-
padding: '1rem',
14-
backgroundColor: '#f5f5f5',
15-
marginBottom: '1rem',
16-
display: 'flex',
17-
gap: '1rem',
18-
flexWrap: 'wrap',
19-
}}
41+
<RootNav />
42+
<main style={{ padding: '1rem' }}>
43+
<div
44+
style={{
45+
padding: '1rem',
46+
backgroundColor: '#fff3e0',
47+
borderRadius: '4px',
48+
border: '1px solid #ff9800',
49+
}}
50+
>
51+
<h1>Page Not Found</h1>
52+
<p>The page you're looking for doesn't exist.</p>
53+
<Link to="/" style={{ color: '#1976d2' }}>
54+
Go back home
55+
</Link>
56+
</div>
57+
</main>
58+
</div>
59+
);
60+
}
61+
62+
// Shared styles for navigation links
63+
const linkStyle = { textDecoration: 'none', color: '#333' };
64+
const activeLinkStyle = {
65+
textDecoration: 'none',
66+
color: '#1976d2',
67+
fontWeight: 'bold' as const,
68+
};
69+
70+
/**
71+
* Shared navigation component used by RootComponent, ErrorComponent, and NotFoundComponent.
72+
* Uses activeProps for accessibility (aria-current) and visual feedback.
73+
*/
74+
function RootNav() {
75+
return (
76+
<nav
77+
style={{
78+
padding: '1rem',
79+
backgroundColor: '#f5f5f5',
80+
marginBottom: '1rem',
81+
display: 'flex',
82+
gap: '1rem',
83+
flexWrap: 'wrap',
84+
}}
85+
aria-label="Main navigation"
86+
>
87+
<Link
88+
to="/"
89+
style={linkStyle}
90+
activeProps={{ style: activeLinkStyle, 'aria-current': 'page' }}
91+
activeOptions={{ exact: true }}
92+
>
93+
Home
94+
</Link>
95+
<Link
96+
to="/about"
97+
style={linkStyle}
98+
activeProps={{ style: activeLinkStyle, 'aria-current': 'page' }}
99+
>
100+
About
101+
</Link>
102+
<Link
103+
to="/users"
104+
style={linkStyle}
105+
activeProps={{ style: activeLinkStyle, 'aria-current': 'page' }}
106+
>
107+
Users
108+
</Link>
109+
<Link
110+
to="/search"
111+
style={linkStyle}
112+
activeProps={{ style: activeLinkStyle, 'aria-current': 'page' }}
113+
>
114+
Search
115+
</Link>
116+
<Link
117+
to="/demo/nested"
118+
style={linkStyle}
119+
activeProps={{ style: activeLinkStyle, 'aria-current': 'page' }}
20120
>
21-
<Link to="/" style={{ textDecoration: 'none' }}>
22-
Home
23-
</Link>
24-
<Link to="/about" style={{ textDecoration: 'none' }}>
25-
About
26-
</Link>
27-
<Link to="/users" style={{ textDecoration: 'none' }}>
28-
Users
29-
</Link>
30-
<Link to="/search" style={{ textDecoration: 'none' }}>
31-
Search
32-
</Link>
33-
<Link to="/demo/nested" style={{ textDecoration: 'none' }}>
34-
Nested Routes
35-
</Link>
36-
</nav>
121+
Nested Routes
122+
</Link>
123+
</nav>
124+
);
125+
}
126+
127+
function RootComponent() {
128+
return (
129+
<div className="app">
130+
<RootNav />
37131
<main style={{ padding: '1rem' }}>
38132
<Outlet />
39133
</main>

demos/basic-v16-rspack-tanstack/app/javascript/src/routes/about.tsx

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,17 @@ function AboutPage() {
88
return (
99
<div>
1010
<h1>About This Demo</h1>
11-
<p>
12-
This demo showcases the integration of TanStack Router with React on
13-
Rails, featuring:
14-
</p>
11+
<p>This demo showcases the integration of TanStack Router with React on Rails, featuring:</p>
1512
<ul>
1613
<li>
17-
<strong>React on Rails 16.2.0.rc1</strong> - The latest version with
18-
improved SSR support
14+
<strong>React on Rails</strong> - Server-side rendering with seamless Rails integration
1915
</li>
2016
<li>
21-
<strong>TanStack Router</strong> - A fully type-safe React router with
22-
first-class SSR support
17+
<strong>TanStack Router</strong> - A fully type-safe React router with first-class SSR
18+
support
2319
</li>
2420
<li>
25-
<strong>Rspack</strong> - A fast Rust-based bundler, 10-20x faster
26-
than Webpack
21+
<strong>Rspack</strong> - A fast Rust-based bundler, 10-20x faster than Webpack
2722
</li>
2823
<li>
2924
<strong>TypeScript</strong> - Full type safety across the application
@@ -34,10 +29,9 @@ function AboutPage() {
3429
</ul>
3530
<h2>How SSR Works</h2>
3631
<p>
37-
When you load this page, the server renders the initial HTML using React
38-
on Rails SSR. The TanStack Router uses memory history on the server to
39-
render the correct route. On the client, it hydrates and switches to
40-
browser history for client-side navigation.
32+
When you load this page, the server renders the initial HTML using React on Rails SSR. The
33+
TanStack Router uses memory history on the server to render the correct route. On the
34+
client, it hydrates and switches to browser history for client-side navigation.
4135
</p>
4236
</div>
4337
);

demos/basic-v16-rspack-tanstack/bin/dev

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,6 @@
2121
require "bundler/setup"
2222
require "react_on_rails/dev"
2323

24-
# Default route configuration
25-
# This points to the TanStack Router demo app at the root path.
26-
# Change this to your preferred default route, or pass --route=<route> to override.
27-
DEFAULT_ROUTE = "/"
28-
2924
# Main execution
30-
# Add the default route to ARGV if no --route option is provided
31-
argv_with_defaults = ARGV.dup
32-
argv_with_defaults.push("--route", DEFAULT_ROUTE) unless argv_with_defaults.any? { |arg| arg.start_with?("--route") }
33-
34-
ReactOnRails::Dev::ServerManager.run_from_command_line(argv_with_defaults)
25+
# TanStack Router handles routing at /, so no --route argument is needed.
26+
ReactOnRails::Dev::ServerManager.run_from_command_line(ARGV)

demos/basic-v16-rspack-tanstack/config/initializers/react_on_rails.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,18 @@
66

77
ReactOnRails.configure do |config|
88
################################################################################
9-
# Server Rendering (Recommended)
9+
# Server Rendering (React on Rails Pro Required)
1010
################################################################################
11+
# ⚠️ This demo requires React on Rails Pro with the Node server renderer.
12+
# TanStack Router uses Node.js APIs (setTimeout, clearTimeout, etc.) that are
13+
# not available in the default ExecJS environment.
14+
#
15+
# To configure React on Rails Pro Node renderer:
16+
# config.server_render_method = "NodeJS"
17+
#
18+
# See: https://www.shakacode.com/react-on-rails-pro/
19+
################################################################################
20+
1121
# Configure server bundle for server-side rendering with `prerender: true`
1222
# Set to "" if you're not using server rendering
1323
config.server_bundle_js_file = "server-bundle.js"

0 commit comments

Comments
 (0)