Portrait von Stefan Wanzenried
Stefan Wanzenried
Software Engineer @Gridonic
Mar 27, 2018 4 min read

Understanding Drupal’s auto-placeholdering

A practical example how to take advantage of the dynamic page cache.

Hi, I am a Software Engineer working @gridonic in Bern, Switzerland. I am still a Drupal-Newbie and have recently explored the powerful but complex Cache-API of Drupal 8. I hope that this article may help other devs to better understand the concept behind “auto-placeholdering”.

Drupal uses the Internal page cache module to cache pages for anonymous users. This is great, because it speeds up our sites noticeably [1]. However, as Drupal developers, we need to pay attention to NOT cache dynamic content. Consider a simple example of displaying a random image on each request. We use a preprocess function in the theme file to randomly select an image and output it via Twig:

Code sample

Can you guess what happens?

  • The first page request (cache-miss) will display a random image and build the cache 👍
  • Subsequent requests (cache-hits) always display the same image — the one which got cached with the first request 👎

There are different solutions to overcome this problem:

  1. Use Javascript to render the dynamic part.
  2. Use Ajax to load dynamic parts after the page has been served from cache.
  3. Disable all caching modules — wait, what? 😱 Don’t.
  4. Use the Dynamic page cache in combination with auto-placeholdering to render the dynamic content.

Solution 4 for the win! It took me some time to understand the concept behind auto-placeholdering — also because the official documentation does not include any examples. Let’s get started with one!

Example: Dynamically show or hide a newsletter block

In a recent project I needed to show or hide a newsletter signup form in the footer of each page depending on the following conditions:

  • Authenticated user: Use the mailchimp API to check if the email address of the current user is registered. If so, hide the newsletter block. Additionally, set a cookie which indicates a subscription. If the user logs out, he or she still won’t see the newsletter block because of the cookie.
  • Anonymous user: Hide the newsletter block if the cookie is present.
    I used the preprocess_region__footer hook in the theme to set a variable for the newsletter:
Code sample

If you are familiar with Drupal’s render-arrays, you will notice that the array being assigned to the newsletter variable looks special. In fact, this is not a render-array. The #lazy_builder key in combination with the user cache-context tells Drupal to use auto-placeholdering. The rendering is delegated to the lazy builder callback which will return a render-array at some later point in time. Roughly, it works like this:

  1. During rendering, Drupal assigns a placeholder to the newsletter variable, hence the name auto-placeholdering.
  2. The Dynamic page cache caches the page with this placeholder.
  3. Before the cached response is sent to the user, the placeholder is replaced by the callback of the lazy builder.

This means that Drupal is able to serve the whole page from cache except the placeholders containing the dynamic parts.

The user cache-context varies the cache by user and is a condition to actually trigger auto-placeholdering. Note that there exist other conditions which tell Drupal to apply this technique (see docs).

Let us take a closer look at the lazy builder:

'#lazy_builder' => [
  'Drupal\my_module\NewsletterRenderer::renderIfSubscribed',
  [Drupal::currentUser()->getEmail()],
],

The first element of the array references the callback function which returns the render array for the placeholder. The second element is an inner array holding the arguments for the callback function. I decided to use a simple class for my lazy builder callback, it looks like this:

Code sample

The renderIfSubscribed method receives the email address of the current user and calls a service to check for the subscription. If not subscribed, it returns a render-array holding the signup form, otherwise just empty markup. The implementation details of the newsletter service are not important, it first performs the cookie check and then uses the mailchimp API to lookup for a subscription.

Cool, but what are the benefits of using this technique?

First of all it guarantees a fast loading website because pages are served from cache, minus the dynamic parts. The response is still fully rendered server-side, there is no need to use Javascript or Ajax to render the dynamic parts (this also avoids DOM flickering). However, it really depends on the website and use-case— you may find that using the Internal page cache in combination with Javascript for the dynamic parts is just fine.

Preparation to successfully use and test auto-placeholdering

  • Uninstall the Internal page cache module since this technique only works with the Dynamic page cache module.
  • Local environment: Make sure to enable all caches in your settings.local.php file.
  • Debugging: Enable the caching headers and use your browser’s inspection tools to check for the X-Drupal-Dynamic-Cache response header. A value of HIT tells you that the page has been served from the cache. If at the same time your dynamic content behaves as expected, congrats — you did it! 🎉

[1] wimleers.com/blog/drupal-8-page-caching-enabled-by-default