General Guide: See Global CLS Guide for universal concepts and fixes.
What is CLS?
Cumulative Layout Shift measures visual stability. Google recommends CLS under 0.1. Magnolia CMS generates CLS from component areas rendering at variable heights, DAM image rendition loading, and personalization variant swaps.
Magnolia-Specific CLS Causes
- Component area height variability -- Magnolia areas contain components that render at different heights depending on content
- DAM rendition loading -- images from Magnolia's DAM load at different sizes depending on the rendition
- Personalization module -- Magnolia's personalization swaps content variants client-side after initial render
- Page editor overlay -- if editor scripts leak to production, the page editor overlay shifts content
- FreeMarker conditional rendering -- templates with
<#if>blocks render different amounts of content, changing page height
Fixes
1. Reserve Space for Component Areas
<#-- In your area template -->
<div class="content-area" style="min-height: 400px; contain: layout;">
[@cms.area name="main" /]
</div>
<div class="sidebar-area" style="min-height: 300px; contain: layout;">
[@cms.area name="sidebar" /]
</div>
/* Component-level containment */
.magnolia-component { contain: layout; min-height: 50px; }
/* Specific component types */
[data-component-type="hero"] { min-height: 500px; aspect-ratio: 16/6; }
[data-component-type="text-image"] { min-height: 250px; }
[data-component-type="carousel"] { min-height: 400px; }
2. Set DAM Image Dimensions
<#-- Always include dimensions from DAM metadata -->
<#assign asset = damfn.getAsset(content.image)>
<#if asset?has_content>
<#assign rendition = damfn.getRendition(asset, "thumbnail")>
<img
src="${rendition.getLink()}"
width="${rendition.getWidth()}"
height="${rendition.getHeight()}"
alt="${asset.getCaption()!''}"
loading="lazy"
style="aspect-ratio: ${rendition.getWidth()} / ${rendition.getHeight()};"
>
</#if>
3. Handle Personalization CLS
/* Reserve space for personalization slots */
.personalization-slot {
min-height: 200px;
contain: layout;
transition: opacity 0.2s;
}
.personalization-slot.loading {
opacity: 0.8;
}
4. Prevent Editor Overlay in Production
<#-- Ensure editor scripts only load in author instance -->
<#if !cmsfn.isEditMode()>
<#-- Production-only code -->
</#if>
5. Preload Theme Fonts
<link rel="preload" href="${ctx.contextPath}/themes/mytheme/fonts/brand.woff2"
as="font" type="font/woff2" crossorigin />
@font-face {
font-family: 'BrandFont';
src: url('fonts/brand.woff2') format('woff2');
font-display: swap;
}
Measuring CLS on Magnolia
- Chrome DevTools Performance tab -- look for layout-shift entries during component area rendering
- Test pages with many components -- more components in an area = higher CLS risk
- Test personalized pages -- personalization variant swaps cause CLS
- Test author vs. public instance -- author instance includes editor overlays that affect CLS
Analytics Script Impact
- Magnolia's built-in analytics is server-side -- minimal CLS
- Third-party analytics added via FreeMarker templates should use
async/defer - Personalization A/B testing scripts that swap DOM elements are the biggest CLS risk