Client-Side Navigation
ruact provides client-side navigation out of the box — no React Router config, no duplicate route definitions. Your Rails routes are the single source of truth.
How link interception works
When the RSC client boots in the browser, it intercepts clicks on <a> elements that point to the same origin. Instead of a full page reload, it:
- Sends
GET /pathwithAccept: text/x-component - Rails detects the header and calls
ruact_render(which returns a Flight payload, no HTML shell) - The client parses the Flight payload and renders the new component tree in place
The URL updates via history.pushState — the browser back/forward buttons work normally.
Regular links just work:
<%= link_to "View Post", post_path(@post) %>
<!-- or -->
<a href="/posts/<%= @post.id %>">View Post</a>No special components needed. No <Link> wrapper. Plain HTML anchors.
State preservation during navigation
The RSC client preserves client component state across navigations when the component tree structure remains the same. For example, if a <SearchInput> appears on every page at the same position, its state (search query, focus) is preserved when the user navigates.
State is reset when:
- The component is unmounted (it no longer appears in the new page's tree)
- The component's key changes
Forms (POST, PUT, PATCH, DELETE)
ruact intercepts form submissions for forms that target the same origin. The request is sent as a standard Rails form submission (with CSRF token), and the response is treated as a new Flight payload:
<%= form_with url: posts_path, method: :post do |f| %>
<%= f.text_field :title %>
<%= f.submit "Create" %>
<% end %>In your controller:
def create
@post = Post.create!(post_params)
ruact_render # ← renders the updated UI as a Flight payload
endRedirect after POST
For the standard Post-Redirect-Get pattern:
def create
@post = Post.create!(post_params)
redirect_to post_path(@post)
endWhen ruact detects a redirect response to a Flight request, it serializes the redirect as a special Flight row:
0:{"redirectUrl":"/posts/1","redirectType":"push"}The client follows the redirect client-side — no full page reload, no flash of unstyled content.
When NOT to use ruact_render
Some controller actions should remain plain Rails responses:
- File downloads (
send_file,send_data) - JSON APIs consumed by third parties
- SSE streams, webhooks
In these cases, use regular render or respond_to blocks. ruact does not interfere with actions that do not call ruact_render.
Turbo and Hotwire coexistence
ruact and Turbo/Hotwire serve different use cases and can coexist in the same app. Use ruact for RSC-powered pages and Turbo for pages that remain traditional Rails HTML. The Ruact::Controller concern only activates for actions that call ruact_render.