Caching
Learn how to cache your site safely.
Drupal caches aggressively, through its render cache and dynamic page cache, and often a CDN or reverse proxy in front. This guide explains how to keep that performance without serving one visitor’s personalized content to another.
The problem with shared caches
A shared cache stores one copy of a response and serves it to everyone. That is what you want for pages that look the same for all visitors, but it is unsafe for personalized pages. If a visitor’s tailored content were stored in a shared cache, the next visitor would receive it too.
The module prevents this automatically. Whenever a response depends on the visitor, because you fetched personalized content, evaluated a query, or identified the user, the module marks it private and adds the session cache context, so Drupal’s caches vary per visitor and shared caches skip it. Responses that render only static content stay cacheable.
Choosing an approach
When a page needs personalization, you have two options depending on whether you want to keep it in the shared cache:
| Personalize on the server | Personalize on the client | |
|---|---|---|
| Final content on first load | ||
| No content flicker | ||
| Visible to crawlers | ||
| Page stored in a shared cache |
Personalizing on the server gives the best experience, while personalizing on the client lets the page stay in the shared cache. Choose per page based on what matters most.
Personalize on the server
When a block fetches content or evaluates a query, the module marks the response private automatically, so it is never stored in a shared cache. Add the session cache context to the render array so Drupal’s own render and page caches also vary it per visitor:
'#cache' => ['contexts' => ['session']],The visitor still gets the page quickly from your site, and every request is personalized. Keep your other pages cacheable by not reading visitor data on them.
Personalize on the client
To keep a page in the shared cache, render it without server-side personalization, so the markup is the same for everyone, then personalize it in the browser.
For example, render a default hero on the server and replace it on the client once the SDK loads:
<section id="hero"> <h1>Welcome to Croct!</h1></section>
{% apply croct %} const heading = document.querySelector('#hero h1');
croct.fetchContent('home-hero').then(({content}) => { heading.textContent = content.title; });{% endapply %}Because the page does not depend on the visitor on the server, it stays cacheable. The trade-off is a brief moment where the default content shows before the personalized content loads.