# ruact
> React Server Components for Rails — "Write Rails. Ship React." ruact renders
> ERB templates as React Server Components (the React Flight wire format) with
> no Node server; Rails routes are the single source of truth for pages, the
> client router, and every server function.
This file is the compact LLM reference for ruact, kept in sync manually with
the AGENTS.md that `rails generate ruact:install` emits into every app.
ruact is NOT Inertia, react-rails, or Next.js — do not transplant their
patterns.
## Mental model
- A page is a normal Rails controller action rendering a normal `.html.erb`.
- Interactive components live in `app/javascript/components/` as `"use client"`
files, mounted from ERB with a PascalCase self-closing tag:
``.
- Mutations: `include Ruact::Server` in a controller makes its routed non-GET
actions (`POST`/`PATCH`/`PUT`/`DELETE`) callable from React — the action IS
the function, at its real route; GET page actions are untouched. The JS
runtime forwards the CSRF token; include the module AFTER
`protect_from_forgery`.
- Reads: public methods on `Ruact::Query` subclasses in `app/queries/`, mounted
in routes with `ruact_queries CatalogQuery` (one GET route per method,
default prefix `/q`), consumed via the `useQuery` hook
(`{ data, loading, error }`). Params come from the method's keyword
arguments; only `string | number | boolean | null` values are accepted.
Queries run the host controller's callback chain (auth) before instantiating.
- ruact generates a typed TS module from the route table; React imports server
functions from it: `import { createPost } from "@/.ruact/server-functions"`.
- Ground truth: `app/javascript/.ruact/server-functions.ts` (gitignored) is
the authoritative accessor list — READ IT instead of simulating the codegen.
Regenerate with `bin/rails ruact:server_functions:generate`.
## Five traps
1. Component tags take no children — client component tags are self-closing
ONLY (``, never `...`). Children (a
matching closing tag) fail LOUDLY with a `PreprocessorError` at preprocess
time; pass content as a prop. Sole exception: the built-in
`...` pair.
2. `{}` props are Ruby, not JavaScript — `label={@post.title}` evaluates Ruby
in the ERB. No JS ternaries, no `{...spread}`, no JSX. Unbraced string
props are not supported.
3. One action, two response shapes — a `Ruact::Server` non-GET action answers
JSON (its instance variables, or `204`, or `{"$redirect": path}`) when the
request's `Accept` header is exactly `application/json` — what every
generated-accessor call sends, including `