Blog Testing 8 min read

Lighthouse CI: Catch Layout Shift from Missing Images

Use Lighthouse CI to detect lighthouse cls images issues caused by missing width/height attributes, late-loading images, and broken image URLs before they reach production.

Lighthouse CLS imagesLighthouse CICore Web Vitals CICLS testingImage performance
Lighthouse CI: Catch Layout Shift from Missing Images

Lighthouse CI runs performance audits against your app in a CI pipeline and fails the build when a metric drops below a threshold. Missing or broken images are one of the most common sources of Cumulative Layout Shift — they collapse reserved space, shift content, and inflate CLS scores. Running Lighthouse CI with lighthouse cls images assertions catches these issues before they reach production.

This guide covers Lighthouse CI setup, the assertions configuration for CLS and image audits, how to use placeholder.pics URLs to test fallback rendering, and integrating Lighthouse CI with GitHub Actions.

How images affect CLS

Why broken images inflate CLS scores in Lighthouse

CLS measures the sum of unexpected layout shifts during page load. An img element without explicit width and height attributes has zero reserved space before the image loads. When the image loads, the browser allocates space and shifts all subsequent content down. The shift score is proportional to the fraction of the viewport affected and the distance shifted.

A broken image URL that never loads actually prevents the layout shift from occurring — the img element collapses to 0×0 and nothing shifts. But a 404 image with explicit dimensions causes a brief flicker as the browser shows the broken-image icon. More critically, if your onerror handler swaps the src to a fallback image with different dimensions, the layout shifts twice: once when the original load fails and once when the fallback loads.

Lighthouse CI catches both scenarios. The cumulative-layout-shift audit measures the total CLS score and fails when it exceeds your threshold. The uses-optimized-images and uses-responsive-images audits flag missing dimensions and oversized images.

Setup

Installing and configuring Lighthouse CI

Install @lhci/cli globally or as a dev dependency. Create a lighthouserc.js configuration file that points to your local dev server and defines assertions for image-related audits.

Run lhci autorun to start the dev server, run Lighthouse against configured URLs, assert results against thresholds, and upload results to a storage backend (local, LHCI server, or temporary public storage).

Implementation text
# Install Lighthouse CI
npm install -D @lhci/cli

# lighthouserc.js
module.exports = {
  ci: {
    collect: {
      startServerCommand: 'npm run build && npm run preview',
      url: [
        'http://localhost:4321/',
        'http://localhost:4321/shop',
        'http://localhost:4321/blog',
      ],
      numberOfRuns: 3,
    },
    assert: {
      assertions: {
        // Fail build if CLS > 0.1
        'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
        // Warn if any image is missing width/height
        'uses-responsive-images': 'warn',
        // Fail if images lack explicit dimensions
        'unsized-images': 'error',
        // Warn on images not lazy loaded below fold
        'offscreen-images': 'warn',
      },
    },
    upload: {
      target: 'temporary-public-storage',
    },
  },
};

Image audit

The unsized-images audit and explicit dimension requirements

Lighthouse's unsized-images audit flags img elements that lack both width and height attributes (or equivalent CSS aspect-ratio). Every image in your app — including fallback images — should have explicit dimensions set.

When using fallback.pics URLs as onerror fallbacks, ensure your fallback img element has width and height set to the same values as the placeholder dimensions. Mismatched dimensions cause a layout shift when the fallback loads.

Implementation text
<!-- Correct: explicit dimensions match fallback URL dimensions -->
<img
  src="https://cdn.yourapp.com/product.jpg"
  width="400"
  height="300"
  alt="Product"
  onerror="if(!this.dataset.fe){this.dataset.fe=1;this.src='https://fallback.pics/api/v1/400x300/7C3AED/FFFFFF?text=No+Image'}"
/>

<!-- Incorrect: dimensions missing, fallback will cause CLS -->
<img
  src="https://cdn.yourapp.com/product.jpg"
  alt="Product"
  onerror="this.src='https://fallback.pics/api/v1/400x300/...'"
/>

GitHub Actions

Running Lighthouse CI in GitHub Actions

Lighthouse CI integrates with GitHub Actions via the treosh/lighthouse-ci-action action or by calling lhci autorun directly in a workflow step. The action posts audit results as a PR comment and fails the check when assertions are not met.

Set LHCI_GITHUB_APP_TOKEN to enable GitHub status checks from the Lighthouse CI GitHub App. This adds a required check to your PR that fails when CLS exceeds the threshold.

Implementation text
# .github/workflows/lighthouse.yml
name: Lighthouse CI
on: [push, pull_request]

jobs:
  lighthouse:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npm run build
      - name: Run Lighthouse CI
        uses: treosh/lighthouse-ci-action@v11
        with:
          configPath: ./lighthouserc.js
          uploadArtifacts: true
          temporaryPublicStorage: true
        env:
          LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}

Fallback images in Lighthouse

Testing fallback image behavior during Lighthouse audits

Lighthouse audits a real page load, including onerror handlers. To test that your fallback images don't introduce CLS, point Lighthouse at a test page where all product image URLs intentionally return 404. The onerror handler fires during the Lighthouse audit, and the CLS score reflects the fallback load behavior.

Use fallback.pics URLs as the fallback src. They respond in under 100ms from the edge, which means the fallback image loads during the same measurement window as the original page load — giving you an accurate CLS score for the failure scenario.

Resources

Lighthouse audit references and fallback.pics docs

Fix CLS from images by setting explicit dimensions and using dimension-matched fallback URLs.

Implementation text
// Dimension-matched fallback URLs (prevent CLS)
https://fallback.pics/api/v1/400x300/7C3AED/FFFFFF?text=No+Image
https://fallback.pics/api/v1/1200x630/3B82F6/FFFFFF?text=OG+Image
https://fallback.pics/api/v1/avatar/80?text=JD

https://fallback.pics/docs/
https://fallback.pics/placeholder-image-api/
https://fallback.pics/blog/core-web-vitals-cls-missing-images/
https://fallback.pics/blog/automated-broken-image-scanner/

Key takeaways

What to standardize before shipping

  • Lighthouse's unsized-images audit flags img elements without explicit width and height; every image including fallbacks must have dimensions set to prevent CLS.
  • An onerror handler that swaps to a fallback URL with different dimensions causes two layout shifts; use dimension-matched fallback.pics URLs to avoid this.
  • Configure lighthouserc.js with cumulative-layout-shift and unsized-images assertions that fail the build when thresholds are exceeded.
  • Test fallback image CLS by pointing Lighthouse at a page where all product image URLs return 404, then verifying the CLS score remains under 0.1.
  • Use the treosh/lighthouse-ci-action GitHub Action to post CLS results as a required PR check and prevent regressions from reaching production.

Production fallback layer

Use fallback.pics anywhere an image URL is accepted.

Start with one deterministic URL and standardize fallback behavior across your design system.

Read the docs