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. Cosmic is a headless CMS, so CLS originates from your frontend -- specifically how you handle async content loading, image rendering, and component hydration.
Cosmic-Specific CLS Causes
- Loading state to content swap -- client-side fetching shows a spinner/skeleton then replaces with API data, causing layout shifts
- Imgix images without dimensions -- Cosmic's Imgix URLs do not embed width/height metadata; your frontend must supply them
- Dynamic component rendering -- pages built from Cosmic Object metafields render components at unknown heights
- Rich text content variability -- Cosmic's Markdown/HTML content fields produce varying-height output
- Third-party embeds in content -- videos, tweets, and widgets embedded in Cosmic content render without reserved space
Fixes
1. Eliminate Loading State CLS with SSG/SSR
// CAUSES CLS: Client-side fetch
function Page() {
const [data, setData] = useState(null);
useEffect(() => {
cosmic.objects.findOne({ type: 'pages', slug: 'home' })
.then(({ object }) => setData(object));
}, []);
if (!data) return <Skeleton />; // CLS when replaced
return <PageContent data={data} />;
}
// NO CLS: Server-side fetch (Next.js)
export async function getStaticProps() {
const { object } = await cosmic.objects.findOne({ type: 'pages', slug: 'home' });
return { props: { page: object }, revalidate: 60 };
}
function Page({ page }) {
return <PageContent data={page} />; // Rendered immediately, no shift
}
2. Set Dimensions on Cosmic Imgix Images
Cosmic's Imgix URLs support dimension extraction. Use the fm=json parameter to get metadata, or set known dimensions:
function CosmicImage({ url, alt, width = 800, aspectRatio = '16/9', eager = false }) {
if (!url) return null;
const optimized = `${url}?w=${width}&q=80&auto=format,compress`;
const height = Math.round(width * (9/16));
return (
<div style={{ aspectRatio, overflow: 'hidden', background: '#f0f0f0' }}>
<img
src={optimized}
alt={alt || ''}
width={width}
height={height}
loading={eager ? 'eager' : 'lazy'}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
</div>
);
}
3. Stabilize Dynamic Page Components
Cosmic Object Types define pages with multiple metafields. Set minimum heights per component:
const COMPONENT_HEIGHTS = {
hero: 500,
feature_grid: 400,
testimonials: 300,
cta: 200,
content_block: 150,
};
function DynamicSection({ section }) {
const minHeight = COMPONENT_HEIGHTS[section.type] || 100;
return (
<section style={{ minHeight, contain: 'layout' }}>
<SectionRenderer section={section} />
</section>
);
}
4. Process Rich Text Embeds
Cosmic's Markdown/HTML fields can contain iframes. Post-process to add dimensions:
function processCosmicContent(html) {
if (!html) return '';
// Wrap iframes in aspect-ratio containers
html = html.replace(
/<iframe([^>]*)>/g,
'<div style="aspect-ratio:16/9;contain:layout;"><iframe$1 style="width:100%;height:100%;border:0;">'
);
html = html.replace(/<\/iframe>/g, '</iframe></div>');
return html;
}
5. Preload Fonts
<link rel="preload" href="/fonts/body.woff2" as="font" type="font/woff2" crossorigin />
@font-face {
font-family: 'BodyFont';
src: url('/fonts/body.woff2') format('woff2');
font-display: swap;
size-adjust: 103%;
}
Measuring CLS on Cosmic
- Chrome DevTools Performance tab -- record page load and look for layout-shift entries during content hydration
- Compare SSG vs CSR -- switching from client-side fetch to static generation should eliminate most CLS
- Test pages with different content density -- pages with many images or embeds expose CLS that simpler pages hide
- Web Vitals RUM -- deploy real-user monitoring since lab tests may miss CLS from lazy-loaded components
Analytics Script Impact
Cosmic is headless, so analytics CLS depends on your frontend:
- Use framework-specific deferred loading for analytics scripts
- Consent banners must use
position: fixedoverlay - A/B testing tools that swap DOM content cause CLS; prefer server-side testing via Cosmic's API-based content variants