Ruact::Controller
Ruact::Controller is an ActiveSupport::Concern that adds RSC rendering capability to any Rails controller.
Include it in ApplicationController:
class ApplicationController < ActionController::Base include Ruact::ControllerendThat is the only setup required. All controller actions that have a corresponding .html.erb template will automatically serve RSC payloads.
Automatic rendering
Section titled “Automatic rendering”Once the concern is included, the default_render hook activates RSC rendering automatically when:
- A matching
.html.erbtemplate exists for the current action - The request is either an HTML request (
Accept: text/html) or an RSC request (Accept: text/x-component)
For all other formats (JSON, XML, etc.), the default Rails rendering is used unchanged.
ruact_render
Section titled “ruact_render”Renders the RSC view for the current action.
def ruact_render(template: nil, locals: {})Parameters
Section titled “Parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
template |
String, nil |
nil |
Logical template name (e.g. "posts/custom"). When nil, uses the current action’s default template. |
locals |
Hash |
{} |
Local variables to pass to the template. |
Behaviour
Section titled “Behaviour”- RSC request (
Accept: text/x-componentorRuact-Request: 1header): responds with atext/x-componentFlight payload. Streaming is used whenActionController::Liveis included. - HTML request: responds with an HTML shell containing the Flight payload inline in a
<script>block.
Examples
Section titled “Examples”# Use the action's default template (most common)def index @posts = Post.all ruact_renderend
# Render a different templatedef create @post = Post.new(post_params) if @post.save redirect_to @post else ruact_render template: "posts/new" endend
# Pass localsdef show @post = Post.find(params[:id]) ruact_render locals: { highlight: params[:highlight] }endredirect_to (overridden for RSC)
Section titled “redirect_to (overridden for RSC)”The concern overrides Rails’ redirect_to for RSC requests. Instead of issuing a 302 HTTP redirect, it emits a Flight redirect row:
0:{"redirectUrl":"/posts/1","redirectType":"push"}The client-side router intercepts this row and navigates without an extra HTTP round-trip.
Behaviour:
- RSC request + same-origin URL: emits Flight redirect row
- RSC request + cross-origin URL: falls back to standard
302redirect - Non-RSC request: falls back to standard Rails
redirect_to
def create @post = Post.create!(post_params) redirect_to post_path(@post) # ← handled by RSC client when called from JS navigationendStreaming
Section titled “Streaming”To enable streaming (progressive render of Suspense boundaries), include ActionController::Live:
class PostsController < ApplicationController include ActionController::Live include Ruact::Controller
def index @posts = Post.all ruact_render endendWith ActionController::Live, Suspense fallbacks are flushed immediately and the deferred content is streamed as it resolves. Without it, the full Flight payload is buffered and sent at once.
Detecting RSC requests
Section titled “Detecting RSC requests”The concern exposes a private helper you can use in before_actions or other callbacks:
before_action :require_authentication, unless: :ruact_request?An RSC request is one that includes Accept: text/x-component or the header Ruact-Request: 1.