Skip to content

Ruact::Controller

Ruact::Controller is an ActiveSupport::Concern that adds RSC rendering capability to any Rails controller.


Setup

Include it in ApplicationController:

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


Automatic rendering

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.


ruact_render

Renders the RSC view for the current action.

ruby
def ruact_render(template: nil, locals: {})

Parameters

ParameterTypeDefaultDescription
templateString, nilnilLogical template name (e.g. "posts/custom"). When nil, uses the current action's default template.
localsHash{}Local variables to pass to the template.

Behaviour

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

Examples

ruby
# 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

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 302 redirect
  • Non-RSC request: falls back to standard Rails redirect_to
ruby
def create
  @post = Post.create!(post_params)
  redirect_to post_path(@post)   # ← handled by RSC client when called from JS navigation
end

Streaming

To enable streaming (progressive render of Suspense boundaries), include ActionController::Live:

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


Detecting RSC requests

The concern exposes a private helper you can use in before_actions or other callbacks:

ruby
before_action :require_authentication, unless: :ruact_request?

An RSC request is one that includes Accept: text/x-component or the header Ruact-Request: 1.

Released under the MIT License.