| name | drupal-lazy-builders |
| description | Drupal lazy builders and placeholder implementation. Use when asked about |
Drupal Lazy Builders
Lazy builders defer rendering of dynamic/personalized content, enabling page caching while preserving dynamic elements.
When to Use
- Displaying user-specific data (name, cart, notifications)
- Content with
session or user cache context
- Fixing UNCACHEABLE pages caused by max-age:0
- Improving authenticated user performance
How It Works
Page renders → Placeholder inserted → Page cached →
Request → Cached page served → Placeholder replaced with fresh content
Argument Restrictions
Only scalar types allowed: string, int, float, bool, NULL
Not allowed: Objects, arrays, closures
Examples
Example 1: Basic User Greeting
Input: "I want to show 'Hello [username]' without breaking page cache"
Output:
$build['greeting'] = [
'#lazy_builder' => [
'my_module.lazy_builders:userGreeting',
[\Drupal::currentUser()->id()],
],
'#create_placeholder' => TRUE,
];
services:
my_module.lazy_builders:
class: Drupal\my_module\LazyBuilders
arguments: ['@entity_type.manager']
<?php
namespace Drupal\my_module;
use Drupal\Core\Security\TrustedCallbackInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
class LazyBuilders implements TrustedCallbackInterface {
protected EntityTypeManagerInterface $entityTypeManager;
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
$this->entityTypeManager = $entity_type_manager;
}
public static function trustedCallbacks() {
return ['userGreeting'];
}
public function userGreeting(int $uid): array {
$user = $this->entityTypeManager->getStorage('user')->load($uid);
return [
'#markup' => t('Hello @name!', ['@name' => $user->getDisplayName()]),
'#cache' => [
'contexts' => ['user'],
'tags' => ['user:' . $uid],
],
];
}
}
Example 2: Shopping Cart Count
Input: "Show cart item count in header without breaking cache"
Output:
$build['cart'] = [
'#lazy_builder' => [
'my_module.lazy_builders:cartCount',
[session_id()],
],
'#create_placeholder' => TRUE,
];
public static function trustedCallbacks() {
return ['cartCount'];
}
public function cartCount(string $session_id): array {
$count = $this->cartService->getItemCount($session_id);
return [
'#markup' => t('@count items', ['@count' => $count]),
'#cache' => [
'contexts' => ['session'],
'max-age' => 0,
],
];
}
Example 3: Fix UNCACHEABLE Page
Input: "My page is UNCACHEABLE because of a timestamp, how do I fix it?"
Output:
$build['timestamp'] = [
'#markup' => 'Generated: ' . date('Y-m-d H:i:s'),
'#cache' => ['max-age' => 0],
];
$build['timestamp'] = [
'#lazy_builder' => ['my_module.lazy_builders:timestamp', []],
'#create_placeholder' => TRUE,
];
public function timestamp(): array {
return [
'#markup' => 'Generated: ' . date('Y-m-d H:i:s'),
'#cache' => ['max-age' => 0],
];
}
Example 4: Pass Multiple Arguments
Input: "I need to pass node ID and view mode to my lazy builder"
Output:
$build['related'] = [
'#lazy_builder' => [
'my_module.lazy_builders:relatedContent',
[$node->id(), 'teaser', $limit],
],
'#create_placeholder' => TRUE,
];
public function relatedContent(int $nid, string $view_mode, int $limit): array {
$node = $this->entityTypeManager->getStorage('node')->load($nid);
return [
'#theme' => 'item_list',
'#items' => $items,
'#cache' => [
'tags' => ['node:' . $nid, 'node_list'],
],
];
}
Example 5: Handle Array Arguments
Input: "I need to pass an array of IDs to my lazy builder"
Output:
['my_service:method', [[1, 2, 3]]]
$ids = [1, 2, 3];
$build['items'] = [
'#lazy_builder' => [
'my_module.lazy_builders:multipleItems',
[implode(',', $ids)],
],
'#create_placeholder' => TRUE,
];
public function multipleItems(string $ids_string): array {
$ids = array_filter(explode(',', $ids_string));
$nodes = $this->entityTypeManager->getStorage('node')->loadMultiple($ids);
}
Example 6: Block with Lazy Builder
Input: "My block shows user-specific content, how do I make it cacheable?"
Output:
class UserNotificationsBlock extends BlockBase {
public function build() {
return [
'#lazy_builder' => [
'my_module.lazy_builders:notifications',
[\Drupal::currentUser()->id()],
],
'#create_placeholder' => TRUE,
];
}
public function getCacheMaxAge() {
return Cache::PERMANENT;
}
public function getCacheContexts() {
return parent::getCacheContexts();
}
}
Example 7: Debug Lazy Builder Issues
Input: "My lazy builder isn't being called, how do I debug?"
Output:
class LazyBuilders {
}
class LazyBuilders implements TrustedCallbackInterface {
public static function trustedCallbacks() {
return ['myMethod'];
}
}
'wrong_service:method'
['service:method', [$node]]
Example 8: Lazy Builder in Twig
Input: "Can I use lazy builder directly in Twig template?"
Output:
$variables['user_greeting'] = [
'#lazy_builder' => ['my_module.lazy_builders:greeting', [$uid]],
'#create_placeholder' => TRUE,
];
{# In template #}
<div class="greeting">
{{ user_greeting }}
</div>
Render Element Restrictions
Lazy builder elements can only contain:
$build['lazy'] = [
'#lazy_builder' => [...],
'#create_placeholder' => TRUE,
'#cache' => [...],
'#weight' => 10,
];
Common Mistakes
| Mistake | Error | Fix |
|---|
| Object argument | Serialization error | Pass ID, load in callback |
| Array argument | Runtime error | Use implode() |
Missing trustedCallbacks() | Security exception | Implement interface method |
Method not in trustedCallbacks() | Security exception | Add method to array |
Other properties with #lazy_builder | Render error | Remove extra properties |
Debugging
drush pm:uninstall big_pipe
drush watchdog:show --type=php