Getting Started
This guide walks you from a fresh rails new to a working React Server Components application. You will need:
- Ruby >= 3.2
- Node.js >= 20
- Rails >= 7.0
1. Create a new Rails app
rails new myapp --skip-javascript
cd myappThe --skip-javascript flag skips the default Importmap/ESBuild setup — ruact brings its own Vite-based frontend.
2. Add the gem
In your Gemfile:
gem "ruact"Then install:
bundle install3. Run the install generator
rails generate ruact:installThe generator creates:
app/javascript/
components/ ← your "use client" components go here
.ruact/ ← generated, gitignored: the typed server-functions module
vite.config.js ← Vite config with ruact plugin
package.json ← react, vite, @vitejs/plugin-react
config/initializers/
ruact.rb ← Ruact.configure block
Procfile.dev ← runs Rails + Vite together
bin/dev ← foreman launcher for Procfile.dev
AGENTS.md ← ruact conventions for coding agentsYour code vs ruact's (hidden) plumbing. Notice app/javascript/ is just your components/ — there is no application.jsx boot file and no flight-client.js / ruact-router.js runtime sitting next to your components. ruact hides its plumbing the way Rails hides its own: the React bootstrap entry is a virtual module (virtual:ruact/bootstrap, served by the Vite plugin from inside the gem), and the Flight client + router runtime ship inside the gem too — compiled into your bundle (against your React) but invisible in your tree. The generated vite.config.js points its build input at virtual:ruact/bootstrap; the HTML <script> tags come from the ruact_js_assets view helper (the controller's HTML shell calls it for you). The one generated file you'll see is the typed app/javascript/.ruact/server-functions.ts, which your components import from (@/.ruact/server-functions) — kept a real .ts so editor types resolve.
Upgrading from an earlier ruact? If your app still has
app/javascript/{application.jsx,flight-client.js,ruact-router.js}, delete those three files, point yourvite.config.jsbuildinputat'virtual:ruact/bootstrap', and let the controller's HTML shell (or theruact_js_assetshelper in your layout) emit the entry<script>. Re-runningrails generate ruact:install --forceregenerates thevite.config.jsfor you.
The generator finishes by running npm install for you, so the JavaScript dependencies are ready when it returns. Pass --skip-npm to opt out (CI, or a non-npm package manager) — then run your package manager's install manually before bin/dev.
4. Check the controller concern
The generator injects include Ruact::Controller into your ApplicationController (and skips the injection when it's already there). Verify app/controllers/application_controller.rb looks like this:
class ApplicationController < ActionController::Base
include Ruact::Controller
endIf your app has no app/controllers/application_controller.rb — the generator only injects into that conventional file — add the include to your base controller yourself.
5. Write your first server component
Create a controller and view as you normally would:
rails generate controller Home indexEdit app/views/home/index.html.erb:
<h1>Hello from ruact!</h1>
<p>Posts: <%= @posts.count %></p>In app/controllers/home_controller.rb:
class HomeController < ApplicationController
def index
@posts = Post.all
end
endWrite the action exactly as you always would — no special render call. When the action has a matching .html.erb view, ruact renders it as React Server Components automatically, serving an HTML shell on a full-page request and a Flight payload on a client-side navigation.
Need an explicit render?
ruact_render(template:, locals:)is still available for the cases where you'd reach forrender— a non-default template or explicit locals. You only need it when you'd needrender.
6. Add a client component
Create app/javascript/components/LikeButton.tsx:
"use client"
import { useState } from "react"
export function LikeButton({ postId }: { postId: number }) {
const [liked, setLiked] = useState(false)
return (
<button onClick={() => setLiked(!liked)}>
{liked ? "❤️ Liked" : "🤍 Like"}
</button>
)
}The "use client" directive at the top tells ruact that this component runs in the browser. The Vite plugin scans app/javascript/components/ for all files with "use client" and registers them in the manifest.
Use it in any ERB template:
<LikeButton postId={@post.id} />7. Start the development server
bin/devThis runs both Rails and the Vite dev server (as defined in Procfile.dev). Open http://localhost:3000 — you should see your page rendered as a React application.
No React Router config needed. Rails routes are the single source of truth. When the user clicks a link (
<a href="/posts/1">), ruact intercepts the navigation and fetches only the Flight payload for/posts/1— no full page reload, no router config.
What you built
In 7 steps, you have:
- A Rails app where controllers and ERB templates define the React component tree
- Full client-side navigation between pages (no React Router config)
- Interactive client components via
"use client"— only where interactivity is needed - A Vite dev server with HMR for fast feedback on client component changes
Next: Read ERB as Server Components to understand how PascalCase ERB tags become React elements.