Caching

Since 4.14.0

Components work with Rails fragment caching. Editing a component’s template, Ruby class, sidecar files, superclasses, or anything it renders invalidates every cache block that contains it, whether that block is in a view or inside a component’s own template:

<% cache @post do %>
  <%= render PostComponent.new(post: @post) %>
<% end %>

Self-caching

Use cache_on to have a component cache its own output, keyed by the values of the named methods:

class PostComponent < ViewComponent::Base
  cache_on :post

  def initialize(post:)
    @post = post
  end

  private

  attr_reader :post
end

The cache key also includes the component’s digest, format, variant, and locale. As with Rails’ cache helper, caching only happens when perform_caching is enabled.

To cache only some renders, pass if: or unless: with a method name or a proc:

cache_on :post, unless: -> { post.draft? }

A component that declares cache_on can’t take content from its callers, since that content isn’t part of the cache key. Passing a block, with_content, or a slot raises ContentPassedToCachedComponentError, even when caching is disabled. Slots filled by the component’s own default_* methods are fine.

cache_on methods run before the component renders, so they can’t use helpers or the view context.

Reading a component’s digest

.cache_digest returns the digest of everything a component renders from, for tying a cache to a component outside a render:

Rails.cache.fetch(["post-summary", post, PostComponent.cache_digest]) do
  expensive_summary_for(post)
end

Declaring dependencies

Dependencies are found by scanning source for literal references, so dynamic renders and included modules aren’t tracked. Declare them with Rails’ # Template Dependency: comment in the Ruby file or template, naming partials by path and components by class:

class PostComponent < ViewComponent::Base
  # Template Dependency: posts/byline
  # Template Dependency: PostSummaryComponent
  # Template Dependency: PostDetailComponent

  def call
    render(@detailed ? PostDetailComponent : PostSummaryComponent).new(post: @post)
  end
end