Acquia Developer (D10) study materials
Acquia Developer (D10) study resources

Slow anonymous page loads after enabling a contributed module

Acquia Developer (D10) · Put your knowledge to work in context.

All case studies

Slow anonymous page loads after enabling a contributed module

A marketing site on Acquia Cloud (2 web nodes, Varnish, memcache) serves 200,000 anonymous page views/day. After adding a contributed module that renders a "recently viewed" block, anonymous TTFB rises from 180 ms to 900 ms and the Varnish hit ratio drops from 85% to 40%. The block uses \Drupal::currentUser() and the session. The module's block plugin has no cache contexts declared. Logs show no errors.

1. Why did the hit ratio collapse, and what cache metadata should the block declare?

Read the worked answer

The block reads the current user, which by default has the user cache context; without declaring it, the render system may still pick up the session because the block's build accesses the session, and dynamic page cache will vary the whole page by user, defeating anonymous caching. Worse, if the block's render array has no cache contexts but the page's other content does, the block's output can be cached in a way that is either wrong or forces a cache miss. The block should declare #cache['contexts'] = ['user'] (or better, ['user.roles:anonymous'] if it only differs for anonymous vs authenticated), plus appropriate tags (e.g., the node list tag if it lists nodes) and a sensible max-age or max-age: 0 only if truly uncacheable. For a "recently viewed" block that is genuinely per-session, it should not be rendered in the anonymous page cache path at all; use a lazy builder or render it client-side via JS so the page remains cacheable.

2. What is the correct way to keep the block for authenticated users without hurting anonymous caching?

Read the worked answer

Make the block a lazy builder (#lazy_builder) so it is rendered after the page is served from cache, and declare #cache['contexts'] = ['user'] on the lazy builder placeholder so the placeholder varies correctly. For anonymous users, either omit it entirely (check \Drupal::currentUser()->isAnonymous() and return an empty render array with the user.roles:anonymous context) or render it with JS from a JSON endpoint. This keeps the main page response cacheable in Varnish and dynamic page cache while still serving personalised content. Verify by checking the X-Drupal-Cache-Contexts header and confirming Varnish HIT on repeat anonymous requests.