Fix CLS Issues on Acquia (Layout Shift) | OpsBlu Docs

Fix CLS Issues on Acquia (Layout Shift)

Stabilize Acquia Drupal layouts by preloading fonts, reserving space for personalization blocks, and optimizing content hub rendering.

General Guide: See Global CLS Guide for universal concepts and fixes.

What is CLS?

Cumulative Layout Shift measures visual stability -- how much visible content moves around during page load. Google recommends CLS under 0.1. Acquia's Drupal-based architecture introduces CLS from dynamic block rendering, Acquia Lift personalization, and Drupal's lazy media handling.

Acquia-Specific CLS Causes

  • Acquia Lift personalization blocks -- personalized content swaps in after initial render, pushing other elements around
  • Drupal's responsive image module -- images without explicit dimensions in Twig templates cause reflow
  • Layout Builder sections -- dynamic sections render at different heights depending on content
  • Cookie consent banners -- Acquia's compliance tools (or contrib modules like eu_cookie_compliance) inject banners that push page content down
  • Admin toolbar rendering -- authenticated users see the Drupal toolbar load after initial paint, shifting the entire page

Fixes

1. Reserve Space for Acquia Lift Personalization

Acquia Lift replaces content slots with personalized variants after the initial page load. This causes CLS unless you reserve fixed dimensions:

/* Reserve space for Lift personalization slots */
.acquia-lift-slot,
[data-lift-slot] {
  min-height: 300px; /* Match your typical slot content height */
  contain: layout;   /* Prevent layout recalculation from affecting siblings */
}

/* Hero personalization slot */
.hero-personalization-wrapper {
  min-height: 500px;
  aspect-ratio: 16 / 9;
  background: #f5f5f5; /* Placeholder color while content loads */
}

2. Fix Image Dimensions in Twig Templates

Drupal's image formatter does not always output width and height attributes. Enforce them in your templates:

{# BAD -- no dimensions, causes CLS #}
<img src="{{ file_url(node.field_image.entity.fileuri) }}" alt="{{ node.field_image.alt }}">

{# GOOD -- explicit dimensions with aspect-ratio fallback #}
{% set image = node.field_image.entity %}
{% if image %}
  <img
    src="{{ image.fileuri | image_style('large') }}"
    width="{{ image.width }}"
    height="{{ image.height }}"
    style="aspect-ratio: {{ image.width }} / {{ image.height }};"
    alt="{{ node.field_image.alt }}"
    loading="lazy"
  >
{% endif %}

Or use the responsive image formatter which handles dimensions automatically:

{{ content.field_image|merge({'#theme': 'responsive_image', '#responsive_image_style_id': 'hero_responsive'}) }}

3. Stabilize Layout Builder Sections

Layout Builder sections can cause CLS when they contain Views or other dynamic content that renders at varying heights:

/* Set minimum heights for Layout Builder sections with dynamic content */
.layout--twocol-section .layout__region {
  min-height: 200px;
}

/* Prevent empty sections from collapsing */
.layout-builder__section:empty {
  min-height: 0;
  display: none;
}

/* Contain layout shifts within sections */
.layout-builder__section {
  contain: layout style;
  overflow: hidden;
}

The eu_cookie_compliance module and similar banners commonly push content down. Use a fixed or overlay approach:

/* Position the consent banner as an overlay, not pushing content */
.eu-cookie-compliance-banner {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 9999;
  /* Do NOT use position: relative or static -- they push page content */
}

Alternatively, reserve space at the top of the page if you want a top-banner approach:

// In mytheme.theme
function mytheme_preprocess_html(&$variables) {
  // Reserve space for cookie banner to prevent CLS
  if (!\Drupal::service('eu_cookie_compliance.handler')->hasAgreed()) {
    $variables['attributes']['class'][] = 'cookie-banner-active';
  }
}
html.cookie-banner-active body {
  padding-top: 60px; /* Height of your cookie banner */
}

5. Preload Fonts to Prevent FOUT Shifts

Drupal themes often load web fonts via @import in CSS, which causes a flash of unstyled text (FOUT) and layout shift:

# mytheme.libraries.yml -- preload fonts
global-styling:
  header: true
  css:
    theme:
      css/style.css: {}
  preload:
    - href: /themes/custom/mytheme/fonts/custom-font.woff2
      as: font
      type: font/woff2
      crossorigin: anonymous

And in your CSS:

@font-face {
  font-family: 'CustomFont';
  src: url('../fonts/custom-font.woff2') format('woff2');
  font-display: swap; /* Show fallback immediately, swap when loaded */
  size-adjust: 105%;  /* Minimize shift when swapping -- tune this value */
}

6. Handle Admin Toolbar CLS

For authenticated users, the Drupal toolbar adds ~80px to the top of the page after initial render:

/* Reserve toolbar space for authenticated users via Drupal's body classes */
body.toolbar-fixed {
  padding-top: 79px; /* Default toolbar height */
}

body.toolbar-fixed.toolbar-tray-open {
  padding-top: 119px; /* Toolbar + open tray */
}

These classes are added by Drupal core, but if your theme overrides page.html.twig aggressively, you may lose them.

Measuring CLS on Acquia

  1. Chrome DevTools Performance tab -- record a page load and filter for "Layout Shift" entries. Each entry shows which elements moved and by how much.
  2. Web Vitals JavaScript API -- add to your theme for real-user monitoring:
// Add to your theme's JS for RUM CLS tracking
new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    if (!entry.hadRecentInput) {
      console.log('CLS shift:', entry.value, entry.sources);
    }
  }
}).observe({ type: 'layout-shift', buffered: true });
  1. Test personalized vs. non-personalized pages -- Acquia Lift pages will have higher CLS; test both variants
  2. Test authenticated vs. anonymous -- authenticated pages skip Varnish and render the toolbar, changing CLS characteristics

Analytics Script Impact

Analytics scripts on Acquia/Drupal commonly cause CLS through:

  • Injected consent managers that add top-of-page banners
  • Heatmap tools (Hotjar, FullStory) that inject overlay elements
  • A/B testing scripts (Optimizely, VWO) that modify DOM after initial paint -- this is the biggest CLS risk from analytics

Load A/B testing via server-side integration or Acquia Lift's server-side personalization to avoid client-side CLS.