ViewComponent/view_component · error · ViewComponent::MissingPreviewTemplateError
A preview template for example EXAMPLE doesn't exist. To fi
Error message
A preview template for example EXAMPLE doesn't exist. To fix this issue, create a template for the example.
What it means
When a preview example method returns nil (or a hash without a :template key), render_args falls back to looking up a template file at <preview_path>/<preview_name>_preview/<example>.html.* across Base.previews.paths. If no such file exists in any preview path, preview_example_template_path raises ViewComponent::MissingPreviewTemplateError. The fix is either to return a render hash from the example or to provide the template file.
Source
Thrown at lib/view_component/preview.rb:88
def preview_name
name.chomp("Preview").underscore
end
# rubocop:disable Style/TrivialAccessors
# Setter for layout name.
def layout(layout_name)
@layout = layout_name
end
# rubocop:enable Style/TrivialAccessors
# Returns the relative path (from preview_path) to the preview example template if the template exists
def preview_example_template_path(example)
preview_path =
Array(preview_paths).detect do |path|
Dir["#{path}/#{preview_name}_preview/#{example}.html.*"].first
end
raise MissingPreviewTemplateError.new(example) if preview_path.nil?
path = Dir["#{preview_path}/#{preview_name}_preview/#{example}.html.*"].first
Pathname.new(path)
.relative_path_from(Pathname.new(preview_path))
.to_s
.sub(/\..*$/, "")
end
# @private
def __vc_load_previews
Array(preview_paths).each do |preview_path|
Dir["#{preview_path}/**/*preview.rb"].sort.each { |file| require file }
end
end
private
def preview_pathsView on GitHub (pinned to 9f22c36fa7)
Solutions
- Make the example method return a render call: def default; render(UserComponent.new); end — then no template file is needed.
- Or create the preview template file at <preview_path>/user_preview/default.html.erb (matching preview_name_preview/<example>.html.*).
- Verify config: Rails.application.config.view_component.preview_paths actually contains the directory holding the template.
- Check the file name matches the example method exactly (default.html.erb for def default).
Example fix
# before
class UserPreview < ViewComponent::Preview
def default
# returns nil -> falls back to template lookup -> MissingPreviewTemplateError
end
end
# after
class UserPreview < ViewComponent::Preview
def default
render(UserComponent.new(user: users(:one)))
end
end Defensive patterns
Strategy: validation
Validate before calling
example = "default"
has_template = Array(ViewComponent::Base.previews.paths).any? do |p|
Dir["#{p}/#{UserPreview.preview_name}_preview/#{example}.html.*"].any?
end
raise ArgumentError, "need render(...) or a template file" unless has_template || UserPreview.new.respond_to?(example) Try / catch
begin
UserPreview.render_args(example)
rescue ViewComponent::MissingPreviewTemplateError
render html: "Create previews/#{UserPreview.preview_name}_preview/#{example}.html.erb", status: :not_found
end Prevention
- End every preview example with an explicit render(...) call — never rely on the nil fallback.
- Add a CI spec asserting each preview example either returns a render hash or has a matching template file under preview_paths.
- Keep preview template directories in sync with preview_paths config after restructures.
When it happens
Trigger: A preview example whose body is empty or returns nil (def default; end) with no previews/<name>_preview/<example>.html.erb file; example methods that only set up fixtures and forget render(...); preview_paths (ViewComponent::Base.previews.paths, default test/components/previews) not including the directory where the template lives.
Common situations: Scaffolding a new preview and forgetting either the render call or the template; moving preview templates during a directory restructure so the Dir[] glob no longer matches; setups where previews live in spec/components/previews but only test/components/previews is configured.
Related errors
- #{example} is not a valid preview example
- Inline templates can only be defined once per-component.
- wrong number of arguments (given #{args.size}, expected 1)
- Cannot serialize render_later with a block
- Cannot deserialize unknown component: #{hash["component_clas
AI-assisted analysis of ViewComponent/view_component@9f22c36fa7 (2026-08-23).
Data as JSON: /api/errors/79bc34a35c21c514.
Report an issue: GitHub.