Table of contents
- What does render cache actually do in Drupal?
- Why does missing or sloppy cache metadata cause problems?
- How do you implement render caching in a custom Drupal block?
- What can go wrong after deployment - and how do you verify the cache is actually working?
- Invalidation failures to watch for
- Verifying the cache is working after deployment
- What are the best practices for Drupal render cache?
- Always define cache metadata on custom render arrays
- Use granular cache tags, not broad invalidation
- Be deliberate with user.roles and user.permissions as contexts
- Don't leave caching disabled after local development
- Back the render cache with Redis or Memcache on high-traffic sites
- Final thoughts
- Frequently Asked Questions
- What is render cache in Drupal?
- How is render cache different from Dynamic Page Cache and Internal Page Cache?
- What happens if I don't add cache metadata to my render arrays?
- When should I use Cache::PERMANENT for max-age?
- What cache contexts should I use?
- Do I need Redis or Memcache for render caching to work?
- How do I debug render cache issues in Drupal?
Drupal sites slow down for a lot of reasons. Unoptimized images, bloated modules, bad hosting. But one of the most overlooked causes is also one of the easiest to fix: render caching that's either misconfigured or not used at all.
Every time a visitor loads a page, Drupal builds it from scratch - querying the database, processing views, assembling blocks. Render caching stops that cycle. Once an element is built, Drupal stores the output and reuses it. The rebuild only happens when the content actually changes.
In this guide, we will walk you through how render caching works at the code level, the metadata that controls it, how you can implement it in a custom block, and what you need to check when it breaks in production.
Key Takeaways
- Render caching stores the HTML output of Drupal elements so they don't get rebuilt on every page request
- Cache metadata - keys, tags, contexts, and max-age - controls what gets cached and when it clears
- Sloppy or missing metadata breaks Dynamic Page Cache and Internal Page Cache, too; not just render cache
- Use specific cache tags like
node:5instead of clearing everything at once - For high-traffic sites, pair render cache with a backend like Redis or Memcache for the biggest gains
What does render cache actually do in Drupal?
When Drupal assembles a page, it constructs render arrays - structured instructions that describe what each element should look like. Render cache takes the final HTML from those arrays and stores it. The next time the same element is requested, Drupal skips the rebuild and serves what's already there.
Each render array can carry cache metadata that tells Drupal exactly how to handle it:
'#cache' => [
'keys' => ['my_custom_block'],
'contexts' => ['url.path', 'user.roles'],
'tags' => ['node:1', 'taxonomy_term:3'],
'max-age' => Cache::PERMANENT,
],Here's what each piece does:
- Keys - the unique identifier for the cached item
- Contexts - handles variations; two users in different roles each get their own cached version
- Tags - ties the cache entry to specific content; edit node 1, and that entry clears automatically
- max-age set to
Cache::PERMANENT- nothing expires on a timer; it only clears when a tag gets invalidated
Why does missing or sloppy cache metadata cause problems?
Without cache metadata, Drupal either caches too broadly and serves outdated content or skips caching entirely and keeps rebuilding. Both cause problems, just in different ways.
What many teams miss is the knock-on effect. Dynamic Page Cache and Internal Page Cache both look at render cache metadata when deciding what to serve. If that metadata is incomplete or poorly defined, those systems can't work properly either. One problem compounds into three.
Under real traffic, the cost adds up. More database queries, more CPU time, slower pages - all for content that hasn't changed.
How do you implement render caching in a custom Drupal block?
Here's a practical example: a custom block that pulls the most recently published article and displays it, with cache metadata set up correctly.
Step 1: Create the block plugin
modules/custom/my_custom_module/src/Plugin/Block/LatestArticleBlock.php
<?php
namespace Drupal\my_custom_module\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\node\Entity\Node;
use Drupal\Core\Cache\Cache;
/**
* Provides a 'Latest Article' block.
*
* @Block(
* id = "latest_article_block",
* admin_label = @Translation("Latest Article Block")
* )
*/
class LatestArticleBlock extends BlockBase {
public function build() {
$query = \Drupal::entityQuery('node')
->condition('type', 'article')
->condition('status', 1)
->sort('created', 'DESC')
->range(0, 1);
$nids = $query->execute();
$output = [
'#markup' => $this->t('No articles available.'),
];
if (!empty($nids)) {
$node = Node::load(reset($nids));
$output = [
'#markup' => $this->t('<a href=":url">@title</a>', [
':url' => $node->toUrl()->toString(),
'@title' => $node->getTitle(),
]),
'#cache' => [
'keys' => ['latest_article_block'],
'tags' => $node->getCacheTags(),
'contexts' => ['url.path'],
'max-age' => Cache::PERMANENT,
],
];
}
return $output;
}
}Step 2: Place the block
Run drush cr, then go to Structure > Block layout > Place block > "Latest Article Block."
After the first load, Drupal caches the rendered output. When an article is updated or a new one goes live, the cache tags handle invalidation automatically - only that block clears, nothing else on the site gets touched.
What can go wrong after deployment - and how do you verify the cache is actually working?
Getting the metadata right in development is only half of it. Cache-related bugs in production are common, and they don't always announce themselves.
Invalidation failures to watch for
The most common post-deployment issue: the cache doesn't clear when it should. This usually traces back to one of three places.
- First, a custom module that calls
Cache::invalidateTags()on save but misses delete or unpublish hooks, so stale content stays in cache when a node is removed. - Second, a reverse proxy (Varnish, Nginx) that's not respecting Drupal's
Cache-ControlorSurrogate-Keyheaders, so tag-based invalidation never reaches it. - Third, a contrib module that renders content without propagating cache tags up the render tree, which means Drupal doesn't know the block depends on that content at all.
If you're using Redis or Memcache as a cache backend, it's also worth confirming that the PhpBackend fallback isn't silently kicking in, which happens when the connection drops and Drupal falls back to the database, which can look fine functionally but performs very differently under load.
Verifying the cache is working after deployment
The quickest check: open a page anonymously in a browser with the Network tab open. Look for an X-Drupal-Cache: HIT header on the response. If you see MISS on repeated loads, something is preventing the entry from being stored or served.
For a more targeted inspection, drush php-eval lets you check specific cache bins:
php
$cache = \Drupal::cache('render')->get('my_cache_key');
var_dump($cache);If you want to trace which tags are attached to a rendered element without writing debug code, enable $settings['cache.debug_backtrace'] = TRUE; in settings.local.php and review the logs. The Cache Debug module gives you the same information as an overlay on rendered elements, which is faster for visual inspection.
After any major deployment, it's worth doing a pass on pages with personalized or role-based content. Those are the most likely places where context misconfiguration causes one user's cached output to bleed into another's session.
What are the best practices for Drupal render cache?
Below is a list where we’ve laid down the best practices to render cache content in Drupal.
Always define cache metadata on custom render arrays
Without it, Drupal is flying blind - it either over-caches and serves stale content, or under-caches and rebuilds constantly. This applies to custom blocks, custom render elements, and anything returned from a preprocess function that varies by content.
Use granular cache tags, not broad invalidation
Tags like node:5 or taxonomy_term:10 clear only what needs clearing. Calling drupal_flush_all_caches() or wiping an entire cache bin works, but it discards valid entries that didn't need clearing. On a busy site, that's a cold-cache penalty across every page simultaneously.
Be deliberate with user.roles and user.permissions as contexts
They're sometimes necessary, but each one added means more cache variations for Drupal to track and store. A block that adds user.permissions as a context will generate a separate cached entry for every distinct permission set across your user base. Add them when the output genuinely differs, not as a default safety measure.
Don't leave caching disabled after local development
It sounds obvious, but it gets skipped more often than you'd think. A development environment with caching off that makes it to production will hurt performance quietly until someone notices, and by then, the database query count is already doing damage.
Back the render cache with Redis or Memcache on high-traffic sites
Drupal's default database cache backend works, but it doesn't scale well once you're handling significant concurrent traffic. Redis and Memcache both keep cache data in memory rather than on disk, which makes reads and writes significantly faster.
The practical difference between them: Redis supports more complex data structures and disk persistence, which makes it the better choice if you want cache entries to survive a server restart. Memcache is simpler and has lower overhead per operation, which can edge it ahead in pure throughput benchmarks on very high-volume, short-lived cache entries.
Neither replaces the render cache - they're the storage layer underneath it. AdvAgg handles CSS and JS aggregation on top of that, separately.
Final thoughts
The metadata is what makes render caching work. Keys, tags, contexts - once you understand what each one controls, the whole system makes sense. Most caching problems in Drupal come down to teams not defining that metadata consistently in custom code. Get it right once, and a lot of other performance problems stop appearing.
If you want a cache audit or help getting render caching configured correctly across your Drupal site, our Drupal specialists at Specbee can help.
Frequently Asked Questions
What is render cache in Drupal?
Render cache stores the HTML output of Drupal's render arrays so elements don't get rebuilt on every page request. It uses cache metadata - keys, tags, contexts, and max-age - to decide when stored output is still valid and when it needs to be regenerated.
How is render cache different from Dynamic Page Cache and Internal Page Cache?
They operate at different levels. Render cache works at the element level - individual blocks, views, and components. Dynamic Page Cache handles full pages for authenticated users. Internal Page Cache handles full pages for anonymous users. All three are related: Dynamic Page Cache and Internal Page Cache depend on render cache metadata to work correctly.
What happens if I don't add cache metadata to my render arrays?
Two bad outcomes. Drupal caches too broadly and serves stale content, or it skips caching entirely and rebuilds on every request. And since Dynamic Page Cache and Internal Page Cache both rely on render cache data, incomplete metadata breaks those too.
When should I use Cache::PERMANENT for max-age?
Use it when the cached entry should only clear when its associated content changes, not on a time-based schedule. Pair it with specific cache tags so that the entry automatically clears when the relevant content is updated.
What cache contexts should I use?
It depends on what varies the output. url.path stores separate versions per page path. user.roles stores separate versions per role. Only add the contexts you actually need - each one multiplies the number of cache variations Drupal has to manage.
Do I need Redis or Memcache for render caching to work?
No. Drupal's built-in cache backend handles render caching without any additional setup. Redis and Memcache improve performance at higher traffic volumes by providing a faster, more scalable storage layer - but they're not required to get render caching working.
How do I debug render cache issues in Drupal?
The Drupal core cache debug settings in settings.local.php can help. The \Drupal::cache()->getMultiple() method lets you inspect cached items directly. The Cache Debug module is also useful for visualising cache metadata on rendered elements.
Pragathi K
Related Insights
Don't Miss Out!