Concrete CMS Caching Guide

This is a community-contributed tutorial.
Oct 16, 2024

Block Caching

  1. btCacheBlockRecord

    • Default: true
    • BlockDeveloper setting: block_record
    • Functionality:
      • When Config::get('concrete.cache.blocks') is enabled, block fields (usable in view as $this->set($key, $value)) are cached in the Blocks table by bID.
      • Database-wise, all blocks derive from the Block table. This means that the fields from the specific block (e.g., btAutonav) are in the btCachedBlockRecord field of the parent table.
      • Can save a database request. Although the default is true, it still needs to be activated in the settings.
  2. btCacheBlockOutput

    • Default: false
    • BlockDeveloper setting: block_output
    • Functionality:
      • Set via block dialog (context menu) in CMS admin area or in Block Controller/Developer.
      • Requires Config::get('concrete.cache.blocks') to be enabled.
      • Stores rendered view in the database.
      • Changes to the block set the lifetime of the current OutputCache to 0, causing it to be re-cached.

    Important considerations:

    • Asset registration should only occur in BlockController's registerViewAssets function, as this is loaded separately with the cached view.
    • For Ajax requests with tokens, ensure cache expires before token: php loadImagesToken: '<?= App::make(Token::class)->generate($this->controller::GET_IMAGES) ?>' Default token validity: const VALID_HASH_TIME_THRESHOLD = 86400; // 24 hours\ Default cache is 5 years or Lifetime (see next point).
    • If client URL parameters affect the BlockController view (e.g., ?key=value), block output cache must not be activated. These are not cached separately. This is usually an architectural issue. Consider switching to route parameters (/view/?key) or handling the view changes directly on the client side.

    Caching conditions:

    ($this->viewToRender == 'view' && $config->get('concrete.cache.blocks') && $this->block instanceof Block
    && $this->block->cacheBlockOutput() && is_object($c) && $c->isPageDraft() === false)
    
  3. btCacheBlockOutputLifetime

    • Default: 0 (seconds, equivalent to 5 years)
    • BlockDeveloper setting: block_output_lifetime
    • Functionality:
      • Set via block dialog or in Block Controller/Developer.
      • If 0, lifetime is 5 years; otherwise: php $btCachedBlockOutputExpires = time() + $lifetime;
      • For Ajax tokens with 24h threshold, set btCacheBlockOutputLifetime < 86400.
  4. btCacheBlockOutputOnPost

    • Default: false
    • BlockDeveloper setting: block_output_on_post
    • Functionality:
      • Caches only on POST requests.
      • Requires btCacheBlockOutput to be enabled.
      • Condition: php $_SERVER['REQUEST_METHOD'] != 'POST' || ($this->block->cacheBlockOutputOnPost() == true)
  5. btCacheBlockOutputForRegisteredUsers

    • Default: false
    • BlockDeveloper setting: block_output_for_registered_users
    • Functionality:
      • Caches also for logged-in users.
      • Requires btCacheBlockOutput to be enabled.
      • Condition: php !$u->isRegistered() || ($this->block->cacheBlockOutputForRegisteredUsers())

Overrides Cache

  • Config: 'concrete.cache.overrides'
  • Stores file locations in ConcreteCMS using a key/value store.
  • Key is a hash value of the file handler.

Full Page Caching

Settings

  • 'concrete.cache.pages'
  • 'concrete.cache.full_page_lifetime'
  • 'concrete.cache.full_page_lifetime_block'

Options:

  • 0: Manual activation per page
  • blocks: Enabled if all blocks on the page allow (no block has btCacheBlockOutput disabled)
  • all: Always enabled

Implementation:

  • Uses FilePageCache class by default, storing pages as files (not in DB).
  • Caching check occurs in DefaultBooter::bootHttpSapi().
  • Cached pages bypass rendering entirely.

Lifetime calculation:

// See getCollectionFullPageCachingLifetimeValue() in Page class
$app = Application::getFacadeApplication();

if ($this->cCacheFullPageContentOverrideLifetime == 'default') {
    $lifetime = $app['config']->get('concrete.cache.lifetime');
} elseif ($this->cCacheFullPageContentOverrideLifetime == 'custom') {
    $lifetime = $this->cCacheFullPageContentLifetimeCustom * 60;
} elseif ($this->cCacheFullPageContentOverrideLifetime == 'forever') {
    $lifetime = 31536000; // 1 year
} else {
    if ($app['config']->get('concrete.cache.full_page_lifetime') == 'custom') {
        $lifetime = $app['config']->get('concrete.cache.full_page_lifetime_value') * 60;
    } elseif ($app['config']->get('concrete.cache.full_page_lifetime') == 'forever') {
        $lifetime = 31536000; // 1 year
    } else {
        $lifetime = $app['config']->get('concrete.cache.lifetime');
    }
}

if (!$lifetime) {
    // we have no value, which means forever, but we need a numerical value for page caching
    $lifetime = 31536000;
}

Respect Block Cache Lifetime

  • Important to enable for respecting block-specific lifetimes in HTTP cache-control headers.
  • Note: This may reduce cache time to 300 seconds due to autonav controller.

Behind Proxy (WAF, Load Balancing)

In PageCacheRecord, we have a validate function that checks if the canonicalUrl matches the request URL. This validation may not work correctly behind a proxy, as the proxy terminates TLS, potentially preventing page retrieval from the cache. Solutions: * Configure your dashboard to accept proxy original headers: .../index.php/dashboard/system/permissions/trusted_proxies * Use a certificate between the proxy and webserver. A self-signed certificate may suffice if you can configure the proxy to accept it.

HTTP Header Cache Control

Avoid setting cache-control in .htaccess for HTML responses generated by the CMS. Instead, let the CMS handle it through Full Page Caching settings. For other files like images, PDFs, fonts, and JavaScript, setting cache-control in .htaccess is appropriate.

Notes

  • To verify if page caching is working, check the cache-control field in the response header of your HTTP server request.
  • Be aware of potential browser caching issues:
    • After logging in, the edit bar at the top may not appear immediately due to the browser showing a cached version from before login.
    • Conversely, you might see the edit bar even after logging out.
    • To resolve these issues, clear the browser cache or manually navigate to the dashboard (.../dashboard).
    • Once you're in the dashboard and return to the website, the cache should be properly evaluated.

Asset Caching

CSS and JavaScript Post-Processing

  • Config: 'concrete.cache.assets'
  • Combines assets by type and combination allowance.
  • Minification should be done beforehand.
  • Processing occurs in JavascriptAsset and CssAsset classes.

Theme CSS Cache

  • Config: 'concrete.cache.theme_css'
  • Caches compiled LESS files when true.

LESS Output Compression

  • Config: 'concrete.theme.compress_preprocessor_output' (default: true)
  • Config: 'concrete.theme.generate_less_sourcemap' (default: false)

Further Discussion:

https://forums.concretecms.org/t/cache-settings/8134/6

Recent Tutorials
Customize locale icons
Oct 29, 2024
By myq.

How to customize locale (language region) flags

Concrete CMS Caching Guide
Oct 16, 2024

An overview of types of caching in Concrete and considerations when using them.

Redirect all requests to HTTPS
Oct 9, 2024
By myq.

How to follow best practices for a secure web

Upgrade Concrete versions 9.3.1 and 9.3.2
Sep 10, 2024
By myq.

How to get past a bug in versions 9.3.1 and 9.3.2 that prevents upgrading the Concrete core through the Dashboard

How to use Composer with Marketplace extensions
Aug 22, 2024

Composer can be used to manage third-party extensions from the marketplace

Controlling Google Tag Manager Tags Based on Concrete CMS Edit Toolbar Visibility
Aug 13, 2024

This document provides a step-by-step guide on how to control the firing of Google Tag Manager (GTM) tags based on the visibility of the Concrete CMS edit toolbar. It explains how to create a custom JavaScript variable in GTM to detect whether the edit toolbar is present on a page and how to set up a trigger that ensures GTM tags only fire when the toolbar is not visible. This setup is particularly useful for developers and marketers who want to ensure that tracking and analytics tags are not activated during content editing sessions, thereby preserving the accuracy of data collected.

Improvements?

Let us know by posting here.