docs / when it breaks
While a page renders
Errors after the template compiled: a client component rendered outside ruact, a layout ruact cannot mount into, no client manifest, a component that came out of a cache block, and writes that arrive after a streamed page started.
request timeruact 0.0.22
When you finish
- why a client component sometimes renders only through ruact, and the one change that fixes each case
- what to do when ruact falls back to its built-in shell
- which of these are raised and which only reach the log
The template compiled; rendering it went wrong. Most of these are raised inside ActionView, so they reach you as an ActionView::Template::Error page whose cause is the ruact error. Values the gem fills from your app are in angle brackets.
A client component outside a ruact page
Section titled “A client component outside a ruact page”A "use client" component renders only in a page ruact renders. These four messages are the four ways a template with one ends up rendered some other way. Each names the component and the template.
Rendered by Rails, outside ruact
Section titled “Rendered by Rails, outside ruact”ruact: <LikeButton /> is a client component, and "posts/index" is being rendered by Rails, outside ruact. Render it through ruact: `ruact_render(template: "posts/index", status: …)` — or, when it is this controller's page and the controller declares `ruact_pages`, add the action there (`render :action` then goes through ruact).Why. The controller includes Ruact::Controller, but this render did not go through ruact — typically a render in an action that ruact_pages does not list, so Rails rendered the template on its own.
Fix. Render the page with ruact_render, or add the action to ruact_pages. When the template is a partial or a layout, the message asks for ruact_render on the page that renders it.
The controller does not include Ruact::Controller
Section titled “The controller does not include Ruact::Controller”ruact: <LikeButton /> is a client component, and "posts/index" is being rendered by PostsController, which does not include Ruact::Controller. Add `include Ruact::Controller` to it: its pages then render through ruact.Fix. The one line from step 4 of Getting Started: include Ruact::Controller in that controller.
A render option ruact does not take
Section titled “A render option ruact does not take”ruact: <LikeButton /> is a client component, and "posts/index" is a ruact page, but its `render` got `pdf:` — not a render option ruact takes — so Rails rendered it outside ruact. Remove it (a flash message goes in `flash.now`), or render the page with `ruact_render`.A mailer, or Controller.render
Section titled “A mailer, or Controller.render”ruact: <LikeButton /> is a client component, and "user_mailer/welcome" is being rendered outside a request to a ruact controller (a mailer, or `Controller.render`). Client components render only in a page a ruact controller renders.Fix. There is no way to render a client component in an email or an off-request render. Use plain HTML there.
The layout
Section titled “The layout”ruact fell back to its built-in shell
Section titled “ruact fell back to its built-in shell”ruact: posts#index fell back to ruact's built-in HTML shell — the layout it rendered emitted no `ruact_js_assets` output (or no `<div id="root"></div>` to mount into), which would have been a blank page. `Ruact.config.layout` is set, so this is a configuration error, not a default: the built-in shell has none of your stylesheets, so your app's CSS does not reach this page. Add `<%= ruact_js_assets %>` next to the `<div id="root"></div>` in your layout (`rails ruact:doctor` names the layout and the missing line), use the layout ruact ships with `config.layout = "ruact"`, or set `config.layout = false` for the built-in shell.Why. config.layout points ruact at a layout, and that layout cannot mount the page. In development and test this raises; in production ruact logs the same text and serves its built-in shell, so the page works but has none of your stylesheets.
Fix. Run rails ruact:doctor: it names the layout and the line it is missing. Or use the layout that ships with the gem, config.layout = "ruact".
The client manifest
Section titled “The client manifest”ruact finds each component’s JavaScript in the client manifest Vite writes. Without it nothing can render.
In development: Vite is not running, and there is no manifest file
Section titled “In development: Vite is not running, and there is no manifest file”[ruact] Vite dev server unreachable at <vite dev server url> and no react-client-manifest.json found at <manifest path> — run `bin/dev`.Fix. Start the app with bin/dev, which runs Rails and Vite together. When the manifest file is there but Vite is not running, the page renders without its JavaScript and shows a red banner instead: The Vite dev server is not running (<server>), so this page has no JavaScript and its React components cannot render. Start the app with bin/dev, which runs Rails and Vite together.
In production: no manifest at boot
Section titled “In production: no manifest at boot”react-client-manifest.json not found — run vite build before deployingFix. Build the JavaScript as part of the deploy (npm run build) and ship the result with the server. Outside production this is a warning in the log instead, and the app boots.
cache blocks
Section titled “cache blocks”A client component that came out of a cache block
Section titled “A client component that came out of a cache block”ruact: the page holds the marker of a client component (<marker>) that this render did not register — it came out of a `cache` block, which stores the component's marker but not the component. Move the client component outside the `cache` block (cache the HTML around it, or the data it is given).Why. A cache block stores the HTML it rendered. For a client component that HTML is only a placeholder; the component itself was registered by the render that wrote the cache, not by this one.
Fix. Keep client components outside cache blocks, and cache what is around them or the data they receive.
A <Suspense> inside a cache block
Section titled “A <Suspense> inside a cache block”A <Suspense> inside `cache` was read from the cache, and its content is not in the cached fragment: put the <Suspense> outside the `cache` block (cache what is inside it).Streamed pages
Section titled “Streamed pages”These two run in development, inside a <Suspense> block. They become that boundary’s error row and a log line; the rest of the page renders.
content_for inside a <Suspense>
Section titled “content_for inside a <Suspense>”content_for :title inside a <Suspense> runs after the layout was sent, so the layout never shows it. Move it outside the <Suspense>.A header, cookie, session or flash written after the page started
Section titled “A header, cookie, session or flash written after the page started”a <Suspense> wrote <what it wrote> after the response started, so the browser never gets it. Status, headers, cookies, the session and the flash are set in the controller, before the page renders.Fix. Set them in the action, before the page renders.
Navigation
Section titled “Navigation”respond_to with no format.html
Section titled “respond_to with no format.html”PostsController#show was asked for its page by the ruact router, which negotiates format.html, and its respond_to has no format.html branch. Add one, or link to this action with data-ruact="false" so the browser loads it.