Lee RowlandsSenior Developer
Ensuring Drupal 8 Block Cache Tags bubble up to the Page
Whilst working on a Drupal 8 project, we found that cache tags for a Block Content entity embedded in the footer weren't bubbling up to the page cache.
Read on to find out how we debugged this and how you can ensure this doesn't happen to you too.
The problem
On our site we had a Block Content entity embedded in the footer that contained an Entity Reference field which allowed site admins to highlight author profiles. The issue was that if the admin edited this block and changed the referenced authors, or the order of the authors - the changes didn't reflect for anonymous users until the page cache was cleared.
This immediately sounded like an issue with cache tags bubbling.
About cache tags
So what are cache tags. Well lets quote the excellent handbook page:
Cache tags provide a declarative way to track which cache items depend on some data managed by Drupal.
So in our case, as the page is built, all of the content that is rendered has its cache tags bubble up to the page level cache entry. When any of the items that form the page are updated, all cache entries that match that item's tags are flushed. This ensures that if a node or block that forms part of a page is updated, the cache entry for the page is invalidated. For entities, the tags are in the format {entity type}:{entity id} - e.g. node:2 or block_content:7
Clearly this wasn't happening for our block.
Debugging cache tags
So the first step with debugging this issue is to see what cache tags were associated with the page.
Luckily, core lets you do this pretty easily.
In sites/default/services.yml you'll find this line:
http.response.debug_cacheability_headers: false
Simply change it to true, and rebuild your container (clear caches or drush cr). Then browse to your site and view the headers in the Network panel of your developer toolbar. You'll start seeing headers showing you the cache tags like so:
Checkout the X-Drupal-Cache-Tags one to see what tags make up the page.
So in our case we could see that the block we were rendering wasn't showing up in the cache tags.
Digging into the EntityViewBuilder for the block and block content entity, we could see that the right tags were being added to the $content variable, but they just weren't bubbling up to the page level.
Rendering individual fields in Twig templates
Now, this particular block had it's own Twig template, we were controlling the markup to ensure that one field was rendered in a grid layout and another was rendered at the end. The block type had two fields, and we were rendering them using something like this:
<div{{ attributes.addClass('flex-grid__2-col') }}> {% if label %} <h2{{ title_attributes.addClass('section-title--light') }}>{{ label }}</h2> {% endif %} {% block content %} <div class="flex-grid"> {{ content.field_featured_author }} </div> <div class="spacing--small-before"> {{ content.field_more_link }} </div> {% endblock %} </div>
i.e We were rendering just field_featured_author and field_more_link from the content variable. And this is the gotcha. You have to render the content variable to ensure that its cache tags bubble up and end up in the page cache.
The fix
There were only two fields on this block content entity, and we wanted control over how they were output. But we also had to render the content variable to make sure the cache tags bubbled. This was a chance for the Twig without filter to rescue the day. The new markup was:
<div{{ attributes.addClass('flex-grid__2-col') }}> {% if label %} <h2{{ title_attributes.addClass('section-title--light') }}>{{ label }}</h2> {% endif %} {% block content %} {{ content|without('field_featured_author', 'field_more_link') }} <div class="flex-grid"> {{ content.field_featured_author }} </div> <div class="spacing--small-before"> {{ content.field_more_link }} </div> {% endblock %} </div>
In other words, we still render the fields on their own, but we make sure we also render the top-level content variable, excluding the individual fields using the without filter.
After this change, we started seeing our block content cache tags in the page-level cache tags and as to be expected, changing the block triggered the appropriate flushes of the page cache.