Drupal 11.3 brings several performance improvements, and one of the most impactful changes is in the Page Cache Middleware. In this article, we’ll explore what has changed, why it matters, and how to update your custom modules and middleware for optimal caching.
What’s Changed?
Previously, whenever a request was processed in Drupal, all middleware and services were constructed, even if the page was already cached. This unnecessary overhead slowed down cached page delivery.
In Drupal 11.3, Page Cache Middleware now uses a Service Closure:
- On a cache hit (page already cached), services higher in the stack are not created, and the cached response is returned immediately.
- On a cache miss (page not cached), services are constructed as usual to generate the response.
This significantly reduces response time and lowers server load.
How It Works
Instead of passing an HttpKernelInterface object directly, Page Cache Middleware now accepts a closure. The closure lazily creates the kernel only if needed.
Old Constructor
public function __construct(
HttpKernelInterface $http_kernel,
CacheBackendInterface $cache,
RequestPolicyInterface $request_policy,
ResponsePolicyInterface $response_policy
) {
$this->httpKernel = $http_kernel;
$this->cache = $cache;
$this->requestPolicy = $request_policy;
$this->responsePolicy = $response_policy;
}
New Constructor with Service Closure
public function __construct(
HttpKernelInterface|\Closure $http_kernel,
CacheBackendInterface $cache,
RequestPolicyInterface $request_policy,
ResponsePolicyInterface $response_policy
) {
if ($http_kernel instanceof HttpKernelInterface) {
@trigger_error(
'Calling without a closure is deprecated in Drupal 11.3 and will throw an error in Drupal 12.0.',
E_USER_DEPRECATED
);
$http_kernel = static fn() => $http_kernel;
}
$this->httpKernel = $http_kernel;
$this->cache = $cache;
$this->requestPolicy = $request_policy;
$this->responsePolicy = $response_policy;
}
Key Points:
- If an object is passed (legacy method), Drupal triggers a deprecated warning.
- The object is wrapped in a closure to lazily create the kernel only if required.
Update Your Service Tag
Previously, the middleware used a responder attribute in page_cache.services.yml. This is no longer necessary:
- - { name: http_middleware, priority: 200, responder: true }
+ - { name: http_middleware, priority: 200 }
Removing responder has no effect on functionality but aligns with the new optimization.
Example: Adapting a Custom Middleware
If you have custom middleware that depends on HttpKernelInterface, update it to accept a closure:
class MyCustomMiddleware implements MiddlewareInterface {
private \Closure $kernel;
public function __construct(HttpKernelInterface|\Closure $kernel) {
if ($kernel instanceof HttpKernelInterface) {
$kernel = fn() => $kernel;
}
$this->kernel = $kernel;
}
public function handle(Request $request, int $type = self::MASTER_REQUEST, bool $catch = true) {
$kernel = ($this->kernel)(); // lazy kernel creation
return $kernel->handle($request, $type, $catch);
}
}
This ensures your middleware works efficiently with cached pages.
Why This Matters
- Faster cached pages: unnecessary service instantiation is avoided.
- Future-proofing: legacy patterns will be removed in Drupal 12.
- Performance boost: especially important for high-traffic sites with many middleware layers.
Conclusion
Drupal 11.3 makes cached page delivery significantly more efficient. Using Service Closures in Page Cache Middleware allows lazy service instantiation and reduces response overhead.
If you develop custom middleware or modules, update constructors and service tags to avoid deprecated warnings and ensure compatibility with future Drupal versions.
Comments