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

Fix CLS Issues on Cosmicjs (Layout Shift)

Stabilize Cosmic JS layouts by reserving space for API-fetched objects, sizing media bucket images, and preloading frontend fonts.

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

  1. Chrome DevTools Performance tab -- record page load and look for layout-shift entries during content hydration
  2. Compare SSG vs CSR -- switching from client-side fetch to static generation should eliminate most CLS
  3. Test pages with different content density -- pages with many images or embeds expose CLS that simpler pages hide
  4. 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: fixed overlay
  • A/B testing tools that swap DOM content cause CLS; prefer server-side testing via Cosmic's API-based content variants