Using minimal templates for Views outputfor Drupal 8 , 9 , 10 , and 11

Last updated :  

Every layer Twig adds between raw data and final HTML costs a little rendering time — usually negligible, but on a Views listing rendering hundreds of rows, those small costs multiply into something you can actually feel. Think of it like assembly-line packaging: wrapping each item individually looks nicer, but if you're shipping ten thousand units, every extra wrap adds up to real time on the line.

What you'll learn in this lesson

  • Why Views' default row/field rendering carries more overhead than it looks like
  • How to override a Views template with a genuinely minimal one
  • When "minimal" is the right trade-off, and when it isn't
  • How to measure whether a template swap actually helped

Where the overhead actually comes from

A default Views display doesn't just print field values — for each row, it runs through a chain of templates: views-view.html.twig wraps the whole display, views-view-unformatted.html.twig (or a similar style template) wraps the row list, views-view-fields.html.twig wraps each row's fields, and each individual field can carry its own formatter markup, wrapper <div>s, and CSS classes for styling hooks you may never actually use. None of this is wasted for a typical content listing — but for a view rendering a large, simple, high-traffic block (a "recent items" sidebar hit on every page load, say), that flexibility is pure overhead.

Overriding with a template suggestion

Views templates follow the same discoverable-suggestion system you met in the Theming topic. To target one specific view's fields output, add a file named with the view and display machine names to your theme:

{# views-view-fields--recent-items--block_1.html.twig #}
{% for field in fields %}
  {{ field.content }}
{% endfor %}

Compare that to the default views-view-fields.html.twig, which wraps every single field in a labeled <div class="views-field views-field-*"> — useful for CSS targeting, dead weight if your custom CSS never uses those classes at all.

Going further: a raw row template

{# views-view-unformatted--recent-items--block_1.html.twig #}
<ul>
{% for row in rows %}
  <li>{{ row.content }}</li>
{% endfor %}
</ul>

This strips the row wrapper down to exactly what semantic HTML you actually need, with no Views-generated wrapper classes at all.

When NOT to do this: if you (or a site builder using Views UI) ever need to add fields, reorder columns, or restyle this specific display without touching code, a minimal hand-written template removes that flexibility — every future change means editing a Twig file instead of clicking through Views UI. Reach for this optimization only on high-traffic, rarely-changed displays where the performance win is worth the lost flexibility, not as a default habit.

Quick check: a site builder wants to add a "Read more" link to a view you've minimized down to a raw <ul>/<li> template. Can they do it from the Views UI alone? If you said no — they'd need a developer to edit the Twig override — you've understood the real trade-off this technique makes.

Key takeaways

  • Views' default templates trade rendering overhead for admin-UI flexibility — reasonable for most views, worth trimming only for high-traffic, stable displays.
  • Template suggestions for Views follow the same --view_name--display_id naming pattern as any other themeable Views output.
  • A minimal row/field template strips wrapper <div>s and CSS-hook classes you're not actually using.
  • This optimization removes Views UI editability for that display — document it clearly so a future site builder isn't confused when their UI changes don't appear.
  • Measure before and after with a real profiling tool, not just intuition — Twig rendering overhead is often smaller than the query cost you tackled in earlier lessons.

Coming up next

Templates control how a view's output gets rendered — but there's a deeper lever available before rendering ever starts: reshaping the query itself. Next, we look at hook_views_query_alter(), which lets you rewrite a view's underlying query directly from code.