| name | drupal-caching |
| description | Cache bins, tags, contexts, invalidation strategies, and external caching for Drupal 10/11. Use when implementing caching, optimizing performance, or configuring cache backends. |
Drupal Caching
Always use cache metadata on render arrays. Never return a render array without #cache keys. Check for proper cache tag, cache context, and max-age on every render array you create.
Cache Metadata (Render Arrays)
Every render array should include cache metadata:
$build = [
'#theme' => 'my_template',
'#data' => $data,
'#cache' => [
'keys' => ['my_module', 'block', $id],
'contexts' => ['user', 'url.query_args'],
'tags' => ['node:' . $nid, 'node_list'],
'max-age' => 3600,
],
];
Cache Contexts
| Context | Varies By | Use When |
|---|
user | Current user ID | Content per user |
user.roles | User roles | Content per role |
user.permissions | Permissions | Access-dependent |
url | Full URL | Page-specific |
url.path | URL path only | Path-dependent |
url.query_args | Query parameters | Filtered content |
languages | Current language | Multilingual |
theme | Active theme | Theme-specific |
session | Session ID | Session-dependent |
Cache Tags
'tags' => ['node:42']
'tags' => ['node_list']
'tags' => ['taxonomy_term:5']
'tags' => ['config:system.site']
'tags' => ['my_module:feature_x']
CacheableMetadata (Object-Oriented API)
Use CacheableMetadata to merge cache metadata from multiple sources. This is cleaner than manually assembling #cache arrays, especially in complex render pipelines.
use Drupal\Core\Cache\CacheableMetadata;
$cache = new CacheableMetadata();
$cache->addCacheableDependency($node);
$cache->addCacheableDependency($user);
$cache->addCacheContexts(['url.query_args']);
$cache->addCacheTags(['my_module:feature_x']);
$cache->setCacheMaxAge(3600);
$cache->applyTo($build);
$access = $entity->access('view', $account, TRUE);
$cache->addCacheableDependency($access);
Lazy Builders
Use #lazy_builder for fragments that vary per user inside otherwise cacheable pages. Lazy builders defer rendering until after the page cache is resolved, so the rest of the page can still be cached.
$build['user_greeting'] = [
'#lazy_builder' => [
'my_module.greeting_builder:build',
[$user_id],
],
'#create_placeholder' => TRUE,
];
The service must implement a build() method returning a render array:
final class GreetingBuilder {
public function build(int $user_id): array {
return ['#markup' => 'Hello, ' . $this->loadUserName($user_id)];
}
}
Cache Invalidation
In services, inject CacheTagsInvalidatorInterface — never use \Drupal::service() calls.
$this->cacheInvalidator->invalidateTags(['node:42', 'my_module:feature_x']);
Cache Bins
| Bin | Purpose |
|---|
default | General-purpose |
render | Render arrays |
page | Full page cache |
dynamic_page_cache | Authenticated pages |
discovery | Plugin discovery |
data | Data processing |
config | Configuration |
menu | Menu trees |
External Cache Backends
Redis Configuration
$settings['redis.connection']['interface'] = 'PhpRedis';
$settings['redis.connection']['host'] = '127.0.0.1';
$settings['cache']['default'] = 'cache.backend.redis';
Varnish Headers
$response->headers->set('Cache-Control', 'public, max-age=3600');
$response->headers->set('Surrogate-Control', 'max-age=86400');
Caching Layers
- Internal Page Cache - Anonymous users (built-in).
- Dynamic Page Cache - Authenticated users (built-in).
- Render Cache - Render arrays (
#cache).
- Cache Backends - Redis, Memcached.
- Reverse Proxy - Varnish, CDN.
Avoiding N+1 Queries
foreach ($nids as $nid) {
$node = Node::load($nid);
}
$nodes = Node::loadMultiple($nids);
Performance Checklist
- CSS/JS aggregation enabled in production.
- Redis/Memcache configured for cache backend.
- No N+1 query problems.
- Image optimization enabled (WebP, lazy loading).
- BigPipe enabled for authenticated users.
- Entity queries use
accessCheck() explicitly.
- Views use caching with appropriate tags.