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.
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_idnaming 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.