Skip to content

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

bash
rails new myapp --skip-javascript
cd myapp

The --skip-javascript flag skips the default Importmap/ESBuild setup — ruact brings its own Vite-based frontend.


2. Add the gem

In your Gemfile:

ruby
gem "ruact"

Then install:

bash
bundle install

3. Run the install generator

bash
rails generate ruact:install

The 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 agents

Your 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 your vite.config.js build input at 'virtual:ruact/bootstrap', and let the controller's HTML shell (or the ruact_js_assets helper in your layout) emit the entry <script>. Re-running rails generate ruact:install --force regenerates the vite.config.js for 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:

ruby
class ApplicationController < ActionController::Base
  include Ruact::Controller
end

If 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:

bash
rails generate controller Home index

Edit app/views/home/index.html.erb:

erb
<h1>Hello from ruact!</h1>
<p>Posts: <%= @posts.count %></p>

In app/controllers/home_controller.rb:

ruby
class HomeController < ApplicationController
  def index
    @posts = Post.all
  end
end

Write 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 for render — a non-default template or explicit locals. You only need it when you'd need render.


6. Add a client component

Create app/javascript/components/LikeButton.tsx:

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:

erb
<LikeButton postId={@post.id} />

7. Start the development server

bash
bin/dev

This 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.

Released under the MIT License.