drupal-cache-maxage
Drupal Cache Max-Age
Max-age defines time-based cache validity in seconds.
When to Use
- Caching external API data with known refresh intervals
- Time-sensitive content (countdowns, live scores)
- Understanding why anonymous pages stay cached
- Debugging UNCACHEABLE responses
Critical Behavior
Page Cache ignores max-age - Only uses tags for invalidation. Dynamic Page Cache respects max-age - max-age:0 causes UNCACHEABLE. Max-age bubbles up - Minimum value wins across render tree.
Max-Age Values
| Value | Meaning |
|---|---|
0 |
Uncacheable - DANGER: bubbles up! |
Cache::PERMANENT (-1) |
Forever until tag invalidation |
3600 |
1 hour |
86400 |
1 day |
Examples
Example 1: External API Data
Input: "I fetch weather data from an API, how do I cache it for 15 minutes?"
Output:
$build = [
'#markup' => $this->weatherService->getCurrentWeather(),
'#cache' => [
'max-age' => 900, // 15 minutes
'tags' => ['weather:current'],
],
];
Example 2: Cache Forever Until Invalidation
Input: "My content only changes when the entity is edited"
Output:
use Drupal\Core\Cache\Cache;
$build = [
'#markup' => $node->getTitle(),
'#cache' => [
'max-age' => Cache::PERMANENT, // -1, forever
'tags' => $node->getCacheTags(), // Invalidates on node save
],
];
Example 3: Time-Sensitive Countdown
Input: "I show a countdown to an event, how do I handle cache?"
Output:
public function buildCountdown(): array {
$event_time = strtotime('2024-12-31 23:59:59');
$remaining = $event_time - time();
return [
'#markup' => $this->formatCountdown($remaining),
'#cache' => [
// Cache for 1 minute or until event, whichever is shorter
'max-age' => $remaining > 0 ? min($remaining, 60) : 0,
],
];
}
Example 4: max-age:0 Causes Page UNCACHEABLE
Input: "Why does X-Drupal-Dynamic-Cache show UNCACHEABLE?"
Output:
// PROBLEM: This makes entire page uncacheable
$build['timestamp'] = [
'#markup' => time(),
'#cache' => ['max-age' => 0], // Bubbles up to page!
];
// SOLUTION: Use lazy builder to isolate uncacheable content
$build['timestamp'] = [
'#lazy_builder' => ['my_module.lazy:renderTimestamp', []],
'#create_placeholder' => TRUE,
];
Example 5: Page Cache Ignores max-age
Input: "I set max-age to 300 but anonymous users still see old content after 5 minutes"
Output:
// max-age:300 does NOT work for Page Cache!
// Page Cache only respects cache tags, not max-age
// Option 1: Use cache tags and invalidate when data changes
Cache::invalidateTags(['my_module:data']);
// Option 2: Disable Page Cache for this route
// In your_module.routing.yml:
my_module.dynamic_page:
path: '/dynamic'
defaults:
_controller: '\Drupal\my_module\Controller::page'
options:
no_cache: TRUE
// Option 3: Kill Page Cache programmatically
\Drupal::service('page_cache_kill_switch')->trigger();
Example 6: Finding max-age:0 Source
Input: "How do I find what's setting max-age to 0?"
Output:
// Method 1: Xdebug conditional breakpoint
// File: core/lib/Drupal/Core/Cache/Cache.php
// Method: mergeMaxAges()
// Condition: $a === 0 || $b === 0
// Method 2: Temporary debug logging
// Add to Cache::mergeMaxAges() temporarily:
if ($a === 0 || $b === 0) {
\Drupal::logger('cache_debug')->warning(
'max-age:0 at: @trace',
['@trace' => json_encode(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5))]
);
}
Example 7: Browser Cache-Control Headers
Input: "How do I control browser caching separately from Drupal cache?"
Output:
// Site-wide: Admin > Config > Development > Performance
// Sets Cache-Control header for anonymous pages
// Per-response in controller:
use Symfony\Component\HttpFoundation\Response;
public function myPage(): Response {
$build = ['#markup' => 'Content'];
$html = \Drupal::service('renderer')->renderRoot($build);
$response = new Response($html);
$response->headers->set('Cache-Control', 'public, max-age=3600');
return $response;
}
Max-Age Bubbling Behavior
// Parent: max-age 3600
$build = [
'#markup' => 'Parent',
'#cache' => ['max-age' => 3600],
];
// Child: max-age 0
$build['child'] = [
'#markup' => 'Child',
'#cache' => ['max-age' => 0],
];
// Result: entire $build has effective max-age: 0
// The minimum always wins!
Common Mistakes
| Mistake | Impact | Solution |
|---|---|---|
| max-age:0 in render array | Entire page uncacheable | Use lazy builder |
| Relying on max-age for Page Cache | Pages never expire | Use cache tags + invalidation |
| Short max-age on stable content | Unnecessary re-renders | Use tags, set PERMANENT |
| Forgetting bubbling | Child max-age:0 breaks parent | Audit all render elements |
Debugging
# Check max-age header
curl -sI https://site.com/ | grep -i 'cache-control\|x-drupal-cache-max-age'
# Clear render cache and test
drush cache:clear render
curl -sI https://site.com/node/1
More from sparkfabrik/sf-awesome-copilot
drupal-cache-debugging
Drupal cache debugging techniques and troubleshooting workflows. Use when asked about X-Drupal-Cache headers interpretation, finding max-age 0 sources, WebProfiler usage, cache hit/miss analysis, stale content debugging, or performance profiling cache-related issues.
19drupal-cache-contexts
Drupal cache contexts implementation guide. Use when asked about request-based cache variations, user.roles vs user context, URL contexts, language contexts, custom cache contexts, or cache context hierarchy. Helps prevent cache explosion from overly broad contexts.
19drupal-cache-tags
Drupal cache tags implementation guide. Use when asked about cache tag naming conventions, entity tags, list tags, custom tags, tag invalidation strategies, or debugging tag-based cache invalidation issues. Covers node:ID, config:name, entity_list patterns.
17drupal-lazy-builders
Drupal lazy builders and placeholder implementation. Use when asked about #lazy_builder render array property, TrustedCallbackInterface, auto-placeholdering, BigPipe integration, personalized content caching, or how to make user-specific content cacheable.
17drupal-dynamic-cache
Dynamic Page Cache and BigPipe module behavior in Drupal. Use when asked about authenticated user caching, auto-placeholdering, lazy builders, BigPipe streaming, X-Drupal-Dynamic-Cache header, or why content shows UNCACHEABLE status. Covers interaction between caching layers.
15http-cache-tools
HTTP cache debugging tools and techniques. Use when asked to inspect cache headers, debug HTTP responses, use curl for cache analysis, or verify caching behavior. Includes SparkFabrik container context with make drupal-cli and docker compose commands.
11