Caching
When to Use
Breadcrumbs are rendered inside the
SystemBreadcrumbBlock, which usescreatePlaceholder(): true— meaning the block renders via BigPipe as a placeholder that resolves after the initial page response. Cache metadata on theBreadcrumbobject directly controls invalidation.
Decision
| Cache concern | Mechanism | How to apply |
|---|---|---|
| Vary by current URL path | url.path.parent context |
Core does this automatically; add it in your builder |
| Vary by whether on front page | url.path.is_front context |
Core does this automatically |
| Vary by route (for route-specific builders) | route context |
Add in both applies() and build() (legacy) or just applies() (Drupal 12+) |
| Invalidate when node changes | Entity cache tag (node:42) |
$breadcrumb->addCacheableDependency($node) |
| Invalidate when config changes | Config cache tag | $breadcrumb->addCacheableDependency($config) |
| Vary by user (access checks) | addCacheableDependency($access) |
PathBasedBreadcrumbBuilder does this per segment |
| Vary by language | languages context |
Easy Breadcrumb adds this; add it in multilingual custom builders |
| Never cache | max-age: 0 |
$breadcrumb->mergeCacheMaxAge(0) — use only when unavoidable |
Pattern
The Breadcrumb class extends RefinableCacheableDependencyTrait. All three cache metadata properties are available:
$breadcrumb = new Breadcrumb();
// Cache contexts — who/what makes the output vary
$breadcrumb->addCacheContexts(['route', 'url.path.parent', 'languages']);
// Cache tags — what entity changes invalidate this
$breadcrumb->addCacheableDependency($node); // adds node:NID tag
$breadcrumb->addCacheableDependency($config); // adds config:name tag
// Access result — varies output AND invalidates when access policies change
$access = $this->accessManager->check($route_match, $this->currentUser, NULL, TRUE);
$breadcrumb->addCacheableDependency($access);
applies() metadata merging: Starting in Drupal 10.4, the BreadcrumbManager collects a shared CacheableMetadata object and passes it to each builder's applies() call. After the winning builder runs, the shared metadata is merged into the returned Breadcrumb. This means:
- Add to $cacheable_metadata in applies() anything the applicability check depends on
- The build() method automatically inherits this; do not double-add it
Drupal 12 signature change (issue #3459277): The applies() signature will become:
public function applies(RouteMatchInterface $route_match, CacheableMetadata $cacheable_metadata): bool;
$cacheable_metadata?->addCacheContexts(...) for forward compatibility.
Easy Breadcrumb cache contexts: The module adds ['route', 'url.path', 'languages'] and also adds the module config as a cacheable dependency — so any config change clears all its breadcrumb caches.
Common Mistakes
- Omitting
url.path.parentin a custom builder — the same builder result gets served for all paths, causing wrong breadcrumbs on unrelated pages - Not adding entity dependencies — if a node title changes, the cached breadcrumb still shows the old title;
addCacheableDependency($node)ensures invalidation - Using
max-age: 0because "breadcrumbs are complex" — this breaks Drupal's render cache for the entire block; use proper cache contexts/tags instead - Missing the
languagescache context on multilingual sites — the same crumb is served for all languages
See Also
- Custom builder cache setup → Custom Breadcrumb Builder
- Block uses BigPipe → Block Placement
- Reference:
core/lib/Drupal/Core/Breadcrumb/Breadcrumb.php - Reference:
core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php