Understanding Caching in Drupal, Introduction
In Drupal 11, the caching system is one of the most sophisticated parts of the architecture. It is designed to solve a specific problem: PHP is slow, but the database is even slower.
To understand the difference between the layers, it helps to look at them as a series of "gates." If a request can be answered at an early gate, Drupal doesn't have to do the heavy lifting of building the page.
1. The Hierarchy of Cache Layers
Drupal 11 uses a "layered" approach. When a user requests a page, Drupal checks these caches in order:
Layer A: Production/External Cache (Varnish/CDN)
* Purpose: This sits outside of Drupal. It stores the entire HTML response.
* Result: If a match is found, Drupal never even "wakes up." This is the fastest possible response.
Layer B: Internal Page Cache (The "Static" Cache)
* Purpose: Stores the final HTML for anonymous users only.
* Mechanism: It maps a URL to a full blob of HTML. It is extremely fast because it bypasses the theme system and the database almost entirely.
Layer C: Dynamic Page Cache
* Purpose: Stores "parts" of a page for authenticated users.
* Mechanism: It caches the "expensive" parts of a page that are the same for everyone, while leaving "placeholders" for things that change (like a "Welcome, [Username]" block).
Layer D: Render Cache
* Purpose: Caches individual "bits" of the site (a single block, a menu, or a node teaser).
* Mechanism: Even if the whole page isn't cached, Drupal can stitch the page together using these pre-rendered fragments.
2. Internal Page Cache vs. Dynamic Page Cache
This is the most common point of confusion. While they sound similar, they serve two very different masteries.
| Feature | Internal Page Cache (page) | Dynamic Page Cache (dynamic_page_cache) |
|---|---|---|
| Target Audience | Anonymous users only. | Everyone (Anonymous & Authenticated). |
| What it stores | The entire HTML page as one block. | The page structure with "placeholders." |
| Speed | Blazing fast (The fastest internal layer). | Fast, but requires more processing. |
| Logic | "Here is the exact page for this URL." | "Here is the page, but swap the 'User Profile' link." |
| Complexity | Simple; strictly URL-based. | Complex; uses Cache Contexts and Auto-placeholding. |
3. How They Work Together: The "Placeholder" Magic
The Dynamic Page Cache is the star of Drupal 11. It uses a technique called BigPipe or Auto-placeholding.
Imagine a page with a list of 100 articles (slow to calculate) and a small block that says "Hi, John" (personal to the user).
* Internal Page Cache can't cache this for John, because if Jane visits, she’d see "Hi, John."
* Dynamic Page Cache caches the 100 articles but puts a "placeholder" where the name goes.
* When John visits, Drupal grabs the 100 articles from the cache instantly and only spends energy calculating the "Hi, John" part.
4. The Three Pillars of Drupal Caching
To keep these layers in sync, Drupal 11 uses three "keys" that you’ll see in your code:
* Cache Tags (Dependency): What is this cache dependent on? (e.g., node:5). If node 5 is edited, every cache layer containing it is cleared automatically.
* Cache Contexts (Variations): Who is seeing this? Is it different per user? Per language? Per URL?
* Cache Max-Age (Time): How long should this be kept? (Default is usually permanent until a tag is invalidated).
Summary
* Use Internal Page Cache for raw speed for guest visitors.
* Use Dynamic Page Cache to make the site feel fast for logged-in users.
* Use Cache Tags so you never have to manually "clear all caches" again.
A code example of how to apply these cache tags to a custom block or controller
In Drupal 11, implementing these cache layers in your own code is done through Cache Metadata. If you are building a custom block, a controller, or a plugin, you need to tell Drupal's "Cache API" how to handle your content so the Dynamic Page Cache knows when to store it and when to dump it.
Here is a practical example of how to apply Tags, Contexts, and Max-age in a custom Block plugin.
Code Example: Custom "User Welcome" Block
This block displays a personalized message and a "Last Updated" timestamp for a specific node.
namespace Drupal\my_module\Plugin\Block;
use Drupal\Core\Block\BlockBase;
use Drupal\Core\Cache\Cache;
/**
* Provides a 'Personalized Node' block.
*
* @Block(
* id = "personalized_node_block",
* admin_label = @Translation("Personalized Node Block"),
* )
*/
class PersonalizedNodeBlock extends BlockBase {
public function build() {
$current_user = \Drupal::currentUser();
$node = \Drupal::routeMatch()->getParameter('node');
return [
'#markup' => $this->t('Hello %name! You are viewing: %title', [
'%name' => $current_user->getDisplayName(),
'%title' => $node ? $node->label() : 'No Node',
]),
'#cache' => [
// 1. CONTEXTS: Vary the cache by the logged-in user and the specific URL (node).
'contexts' => ['user', 'url.path'],
// 2. TAGS: Invalidate this cache if this specific node is edited.
'tags' => $node ? $node->getCacheTags() : [],
// 3. MAX-AGE: Let's say we want this to expire after 1 hour regardless.
'max-age' => 3600,
],
];
}
}
Breaking Down the Metadata
1. Cache Contexts (The "Who/Where")
In the example above, contexts => ['user'] tells the Dynamic Page Cache: "Do not show the version cached for John to Jane."
* Common Contexts: user.roles, languages, theme, timezone.
* The Result: Drupal creates a different "version" of this block for every unique context combination.
2. Cache Tags (The "What")
The $node->getCacheTags() typically returns something like node:5.
* The Magic: If an editor goes into the Drupal admin and saves Node 5, Drupal automatically finds every single cached item (in the Database, in the Dynamic Cache, and even in Varnish/CDNs) that has the node:5 tag and deletes it.
* This ensures users never see stale data after an update.
3. Cache Max-Age (The "When")
This is a fallback.
* 0: Never cache this (useful for high-frequency data like a stock ticker).
* -1 (Default): Cache forever until a Tag is invalidated.
* 3600: Cache for 1 hour.
The "Placeholder" Strategy (BigPipe)
If you have a block that is extremely personalized (like a shopping cart), you don't want to break the cache for the entire page just for that one tiny element.
In Drupal 11, if you set a very "high-cardinality" context (like user), Drupal might automatically turn that block into a placeholder. It will send the rest of the page to the browser immediately, and then "stream" the personalized block a split-second later once it's calculated.
How to debug these layers using the X-Drupal-Cache-Tags headers in your browser's Inspect tool
Debugging Drupal's cache layers is best done through the browser's DevTools. By default, Drupal hides this information, so you first need to enable development mode (usually via development.services.yml) and ensure http.response.debug_cacheability_headers: true is set.
Once enabled, every page request will include specific headers that tell you exactly what is happening under the hood.
The Key Debugging Headers
When you inspect a network request in your browser, look for these three headers:
| Header | Purpose |
|---|---|
| X-Drupal-Cache | Tells you if the Internal Page Cache hit or missed (HIT or MISS). |
| X-Drupal-Dynamic-Cache | Tells you if the Dynamic Page Cache was used (HIT, MISS, or UNCACHEABLE). |
| X-Drupal-Cache-Tags | A massive list of every data dependency on the page (e.g., node:5, config:user.settings, library_info). |
| X-Drupal-Cache-Contexts | Shows every variation rule applied (e.g., languages:en, theme, user.permissions). |
How to Interpret the "Uncacheable" Status
If you see X-Drupal-Dynamic-Cache: UNCACHEABLE, it usually means one of three things:
* A Session is active: You are logged in and something on the page is explicitly told not to cache for sessions.
* Max-Age 0: Somewhere in your code (or a contributed module), an element has '#cache' => ['max-age' => 0]. This "bubbles up" and kills the cache for the entire page.
* High-Cardinality Contexts: You are using a context that changes too frequently to be worth caching at the page level.
The "Bubbling" Effect
In Drupal 11, cache metadata bubbles up. If you have a tiny footer block with a cache tag of node:10, the entire page response will now include node:10 in its headers.
* The Good: If Node 10 is updated, the whole page cache is cleared automatically.
* The Bad: If one small block is uncacheable (max-age: 0), it can make your entire page uncacheable.
Pro-Tip: Using Twig Debug
If you aren't sure which block is causing a cache issue, enable Twig Debugging. In your HTML source, you will see HTML comments like ``. This helps you identify exactly which template file is responsible for the content, so you can hunt down its build() function and check the cache metadata.
Next specific shell commands to clear these individual cache bins via Drush without clearing the entire site:
In Drupal 11, clearing the entire cache (the famous drush cr) is often overkill and can temporarily tank your site's performance because every single layer has to be rebuilt from scratch.
If you are debugging a specific issue, it is much better to target the specific cache bin.
Targeting Specific Bins with Drush
You can use the sql-query command or the cache-clear (or cc) command to be surgical.
* Clear only the Render Cache:
drush cc render
Use this if you've changed a Twig file or a #markup array and the changes aren't appearing.
* Clear only the Dynamic Page Cache:
drush cc dynamic_page_cache
Use this if your authenticated users are seeing "stale" personalized data.
* Clear only the Internal Page Cache (Anonymous):
drush cc page
Use this if your guest users are seeing old versions of pages but logged-in users are seeing the new ones.
* Clear the Discovery Cache:
drush cc discovery
Use this if you've added a new Plugin (like a new Block or Content Entity) and Drupal hasn't "found" it yet.
Clearing by Cache Tag (The "Pro" Way)
If you know exactly which piece of data is stuck, you don't even need to clear a whole bin. You can invalidate specific tags via a small PHP snippet executed through Drush.
Example: Clear everything related to Node 5
drush php-eval "\Drupal\Core\Cache\Cache::invalidateTags(['node:5']);"
This is incredibly powerful because it wipes Node 5 from the Render Cache, the Dynamic Cache, and the Internal Page Cache simultaneously, without touching anything else on the site.
Visualizing the Cache Flow
When you are debugging, keep this "Short Circuit" logic in mind:
* Request hits Page Cache? If Yes → Done. (Fastest)
* Request hits Dynamic Page Cache? If Yes → Assemble placeholders → Done.
* Request hits Render Cache? If Yes → Build page from pieces → Done.
* No Hits? Drupal runs all Hooks, Database queries, and Theme engines. (Slowest)
A Quick Warning on "The Red Button"
While drush cr (Cache Rebuild) is the "turn it off and on again" of Drupal, try to avoid it on production sites with high traffic. It clears the container, meaning Drupal has to re-scan every module folder on the server to figure out how the system is even put together.
Recent content
-
6 hours 18 minutes ago
-
6 hours 46 minutes ago
-
7 hours 22 minutes ago
-
8 hours 18 minutes ago
-
1 week 1 day ago
-
1 week 4 days ago
-
3 weeks 4 days ago
-
3 weeks 5 days ago
-
3 weeks 6 days ago
-
4 weeks 1 day ago