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 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).
# 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.
<!-- 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.
# .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.
// 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.