ViewComponent/view_component · error · ViewComponent::Serializable::UnserializableError

Cannot serialize render_in with a block

Error message

Cannot serialize render_in with a block

What it means

The Proxy returned by render_later implements the Rails renderable interface via render_in, but the whole point of the proxy is that it can be serialized — and blocks can't be. If render_in is called with a block (e.g., render(proxy) { ... } in a template, or broadcast-style calls that capture a block), it raises ViewComponent::Serializable::UnserializableError ('Cannot serialize render_in with a block') before building the component. Content data must be attached beforehand via with_* slot calls that take arguments only.

Source

Thrown at lib/view_component/serializable/proxy.rb:44

      end

      attr_reader :component_class, :initialize_args, :slot_calls

      def initialize(component_class, *args)
        if component_class.name.nil?
          raise UnserializableError, "Cannot serialize anonymous component class #{component_class.inspect}"
        end

        @component_class = component_class
        @initialize_args = args
        @slot_calls = []
      end
      ruby2_keywords :initialize

      # Implements the Rails renderable interface
      def render_in(view_context, &block)
        if block
          raise UnserializableError, "Cannot serialize render_in with a block"
        end
        build_component.render_in(view_context)
      end

      def method_missing(method_name, *args, &block)
        if slot_method?(method_name)
          capture_slot_call(method_name, args, &block)
        else
          super
        end
      end
      ruby2_keywords :method_missing

      def respond_to_missing?(method_name, include_private = false)
        slot_method?(method_name) || super
      end

      # Returns a hash that can be rebuilt into a Proxy instance using +deserialize+

View on GitHub (pinned to 9f22c36fa7)

Solutions

  1. Remove the block from the render call: <%= render(MyComponent.render_later("x")) %>.
  2. Supply the would-be block content as slot arguments instead: render(MyComponent.render_later("x").with_body("<p>content</p>")) using a slot the component renders.
  3. If the block genuinely needs the render-time view context, render the concrete component (MyComponent.new(...)) rather than the proxy.

Example fix

# before (ERB)
<%= render(MyComponent.render_later("title")) do %>
  <p>Block content</p>
<% end %>

# after (ERB)
<%= render(MyComponent.render_later("title").with_body("<p>Block content</p>")) %>
Defensive patterns

Strategy: validation

Validate before calling

def render_deferred(view_context, proxy, &block)
  raise ArgumentError, "proxies cannot render with a block" if block
  view_context.render(proxy)
end

Prevention

When it happens

Trigger: <%= render(MyComponent.render_later("x")) do %>Block<% end %> in an ERB template; passing a content block when broadcasting a deferred component with Turbo Streams; generic code that always yields a block to any renderable object it renders.

Common situations: Swapping a live component for a deferred proxy in an existing template that still has a block; helper methods that render arbitrary renderables with an optional block; migrating broadcast calls to the proxy API.

Related errors


AI-assisted analysis of ViewComponent/view_component@9f22c36fa7 (2026-08-23). Data as JSON: /api/errors/3bf1c4e7c2b583eb. Report an issue: GitHub.