ViewComponent/view_component · error · ViewComponent::Serializable::UnserializableError
Cannot serialize slot call '#{method_name}' with a block
Error message
Cannot serialize slot call '#{method_name}' with a block What it means
Slot calls on a render_later Proxy are captured by method_missing and replayed at render time, but only as (method, args) pairs stored in slot_calls — a Ruby block cannot be captured that way. capture_slot_call therefore raises ViewComponent::Serializable::UnserializableError ("Cannot serialize slot call 'with_x' with a block") when a with_* slot method is invoked with a block on the proxy. Slot content must be passed as serializable arguments.
Source
Thrown at lib/view_component/serializable/proxy.rb:94
end
if defined?(Rails::VERSION) && Rails::VERSION::MAJOR == 7 && Rails::VERSION::MINOR == 1
# Rails expects us to define `format` on all renderables,
# but we do not know the `format` of a ViewComponent until runtime.
def format
nil
end
end
private
def slot_method?(method_name)
method_name.to_s.start_with?("with_") && @component_class.method_defined?(method_name)
end
def capture_slot_call(method_name, args, &block)
if block
raise UnserializableError, "Cannot serialize slot call '#{method_name}' with a block"
end
@slot_calls << {method: method_name, args: args}
self
end
def build_component
@component_class.new(*@initialize_args).tap do |instance|
@slot_calls.each do |call|
instance.public_send(call[:method], *call[:args])
end
end
end
end
end
end
View on GitHub (pinned to 9f22c36fa7)
Solutions
- Drop the block and pass the content as an argument the slot accepts: .with_item(label: "One", body: "Body text").
- If the slot normally takes a block, redesign it to accept content as plain data (or a nested deferred component) rather than a block.
- If block semantics are required, build and render the real component synchronously instead of the proxy.
Example fix
# before
proxy = MyComponent.render_later("title").with_item(label: "One") do
"<span>Block body</span>"
end # UnserializableError on slot call
# after
proxy = MyComponent.render_later("title").with_item(label: "One", body: "<span>Block body</span>") Defensive patterns
Strategy: validation
Validate before calling
def with_serializable_slot(proxy, method_name, *args, &block) raise ArgumentError, "slot calls on a deferred proxy cannot take blocks" if block proxy.public_send(method_name, *args) end
Prevention
- Design slots used with render_later to accept content as keyword/positional args, not blocks.
- Pass plain data (strings, hashes) or defer nested components via their own render_later proxy.
- Lint for `.with_...` calls followed by a block near render_later usage.
When it happens
Trigger: MyComponent.render_later(...).with_item { "content" }; calling with_item(label: "One") { |item| ... } on the proxy; porting block-style slot usage from a concrete component to the deferred proxy.
Common situations: Components whose slots normally receive blocks in templates (renders_many :items with block content); moving Turbo Stream renders into jobs while keeping block-based slot calls; helper methods that always attach a block to slot setters.
Related errors
- Cannot serialize render_later with a block
- Cannot deserialize unknown component: #{hash["component_clas
- Cannot serialize anonymous component class #{component_class
- Cannot serialize render_in with a block
- It looks like a block was provided after calling `with_conte
AI-assisted analysis of ViewComponent/view_component@9f22c36fa7 (2026-08-23).
Data as JSON: /api/errors/b6d22d3ca3f6241a.
Report an issue: GitHub.