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. Liferay DXP generates CLS from portlet containers rendering at variable speeds, Content Page fragment loading, and Adaptive Media image variant swaps.
Liferay-Specific CLS Causes
- Portlet container rendering -- portlets render independently at different speeds, causing their containers to resize
- Content Page fragments -- fragments load and render sequentially, shifting surrounding content
- Adaptive Media resolution switching -- low-resolution placeholder swaps to high-resolution variant
- Cookie consent/alert portlets -- banner portlets injected at the top push all page content down
- Theme font loading -- custom theme fonts cause FOUT shifts
Fixes
1. Reserve Space for Portlet Containers
/* Theme CSS -- portlet container sizing */
.portlet-boundary { min-height: 100px; contain: layout; }
.portlet-column { contain: layout; }
/* Specific portlet types */
.portlet-journal-content { min-height: 200px; }
.portlet-navigation { min-height: 60px; }
.portlet-asset-publisher { min-height: 300px; }
2. Stabilize Content Page Fragments
/* Fragment containers */
.page-editor__fragment { contain: layout; min-height: 100px; }
.lfr-layout-structure-item { contain: layout; }
/* Hero fragment */
[data-lfr-fragment-id="hero"] { min-height: 500px; aspect-ratio: 16/6; }
3. Fix Adaptive Media Image Sizing
Always include dimensions from the Document Library:
<#-- In theme FreeMarker templates -->
<img
src="${imageUrl}"
width="800" height="450"
alt="${title}"
loading="lazy"
style="aspect-ratio: 800/450; width: 100%; height: auto;"
>
4. Fix Alert Banner CLS
/* Liferay alert banners -- overlay instead of push */
.alert-container, .lfr-alert-container {
position: fixed;
top: 0; left: 0; right: 0;
z-index: 9999;
}
5. Preload Theme Fonts
<link rel="preload" href="${themeDisplay.getPathThemeCSS()}/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 Liferay
- Chrome DevTools Performance tab -- look for layout shifts during portlet rendering
- Test pages with multiple portlets -- higher portlet count = higher CLS risk
- Test authenticated vs anonymous -- different portlet sets = different CLS
- Liferay's built-in profiling for portlet render order
Analytics Script Impact
- Liferay Analytics Cloud loads asynchronously -- minimal CLS
- Custom analytics portlets should be in fixed-height containers
- Cookie consent (GDPR module) should use fixed-position overlay