Skip to content

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::Controller
end

That is the only setup required. All controller actions that have a corresponding .html.erb template will automatically serve RSC payloads.


Once the concern is included, the default_render hook activates RSC rendering automatically when:

  1. A matching .html.erb template exists for the current action
  2. 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.


Renders the RSC view for the current action.

def ruact_render(template: nil, locals: {})
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.
  • RSC request (Accept: text/x-component or Ruact-Request: 1 header): responds with a text/x-component Flight payload. Streaming is used when ActionController::Live is included.
  • HTML request: responds with an HTML shell containing the Flight payload inline in a <script> block.
# Use the action's default template (most common)
def index
@posts = Post.all
ruact_render
end
# Render a different template
def create
@post = Post.new(post_params)
if @post.save
redirect_to @post
else
ruact_render template: "posts/new"
end
end
# Pass locals
def show
@post = Post.find(params[:id])
ruact_render locals: { highlight: params[:highlight] }
end

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 302 redirect
  • 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 navigation
end

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
end
end

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


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.