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. Kontent.ai is headless, so CLS originates from your frontend implementation -- async content loading, image rendering, and rich text resolution.
Kontent.ai-Specific CLS Causes
- Loading state to content swap -- client-side Delivery API fetching shows loading UI then replaces with content
- Assets without rendered dimensions -- Kontent.ai provides
width/heightmetadata but implementations often skip them - Rich text inline content resolution -- resolved inline content items render at unknown heights
- Modular content variability -- different content types in modular content elements render at different heights
- Web Spotlight preview scripts -- if accidentally included in production, preview scripts inject UI elements
Fixes
1. Use SSG to Eliminate Loading CLS
// Server-side fetch eliminates loading state entirely
export async function getStaticProps({ params }) {
const response = await client.item(params.slug).depthParameter(1).toPromise();
return { props: { item: response.data.item }, revalidate: 60 };
}
2. Use Asset Dimensions from Kontent.ai
function KontentImage({ asset, eager = false }) {
if (!asset?.url) return null;
return (
<div style={{ aspectRatio: `${asset.width} / ${asset.height}` }}>
<img
src={`${asset.url}?w=800&fm=webp&q=80`}
width={asset.width} height={asset.height}
alt={asset.description || ''}
loading={eager ? 'eager' : 'lazy'}
style={{ width: '100%', height: 'auto' }}
/>
</div>
);
}
3. Stabilize Modular Content
const CONTENT_TYPE_HEIGHTS = {
hero_banner: 500, feature_section: 400, testimonial: 250, cta: 200,
};
function ModularContent({ item }) {
const minHeight = CONTENT_TYPE_HEIGHTS[item.system.type] || 100;
return (
<section style={{ minHeight, contain: 'layout' }}>
<ContentTypeRenderer item={item} />
</section>
);
}
4. Handle Rich Text Embeds
// Custom resolver for inline items in rich text
const resolvers = {
image: (asset) => (
<div style={{ aspectRatio: `${asset.width}/${asset.height}`, contain: 'layout' }}>
<img src={`${asset.url}?w=800&fm=webp`} width={asset.width} height={asset.height} loading="lazy" />
</div>
),
video: () => (
<div style={{ aspectRatio: '16/9', contain: 'layout' }}>...</div>
),
};
5. Preload Fonts
<link rel="preload" href="/fonts/body.woff2" as="font" type="font/woff2" crossorigin />
Measuring CLS on Kontent.ai
- Chrome DevTools Performance tab -- look for shifts during content hydration
- Compare SSG vs CSR -- SSG eliminates most CLS
- Test pages with different content types in modular content
- Web Vitals RUM for production CLS monitoring