⚡ Web Performance

Lighthouse 66 to 92:
Zero CLS, LCP 1.3s, Best Practices 100

An e-commerce site had a Lighthouse Performance score of 66 and a Cumulative Layout Shift (CLS) of 1.248 — meaning the page visually lurched on every load. The fixes required understanding why the CLS happened before reaching for the obvious solutions, because some "best practices" would have made things worse.

66 → 92Performance Score
1.248 → 0CLS Score
1.3sReal LCP
73msReal INP

Starting Point

The initial Lighthouse audit told a clear story: Performance at 66 was dragging down the overall score. The Accessibility score of 88 had low-hanging fruit. Here's what the dashboard looked like before we started:

66
Performance
88
Accessibility
92
Best Practices
90
SEO

The biggest single drag on Performance was the CLS of 1.248. On a scale where 0.1 is "needs improvement" and anything above 0.25 is "poor," 1.248 meant the page was visibly unstable on every single load.

Diagnosing the CLS of 1.248

CLS happens when content shifts position after the browser first renders it. The initial assumption would be missing image dimensions or late-loading ads. This case was more subtle: the CLS was caused by the CSS loading strategy itself.

The site was using <link rel="preload" as="style"> on its main CSS files with an onload handler to apply them asynchronously — a pattern sometimes recommended to avoid render-blocking CSS. The problem: the page renders instantly with no styles, then the CSS loads and the entire layout shifts into place all at once, producing a massive CLS spike.

The counterintuitive fix: We deliberately kept the CSS as render-blocking. Yes, this adds a small FCP delay, but it completely eliminates the unstyled-flash CLS. For this site, preventing 1.248 CLS was worth far more than shaving 200ms off FCP.

Font-Display Issues

FontAwesome and Glyphicons icon fonts were loading without font-display declarations. When a font loads late, the browser either shows invisible text (FOIT) or swaps in a fallback font and then the icon font (FOUT). Both cause CLS and hurt the Accessibility score.

Google Fonts was configured with display=swap — which causes a visible font swap mid-render. For body text, display=optional is usually better: the browser uses the web font only if it's already cached, avoiding the CLS-causing swap entirely. On repeat visits, the font is cached and appears immediately.

Before — font swap CLS
fonts.googleapis.com/css2?
family=Inter&display=swap

/* FontAwesome — no font-display */
@font-face {
  src: url(fa.woff2);
  /* missing font-display! */
}
After — no CLS
fonts.googleapis.com/css2?
family=Inter&display=optional

/* FontAwesome — patched in CSS */
@font-face {
  src: url(fa.woff2);
  font-display: block;
}

FontAwesome and Glyphicons were patched directly in their CSS files on the server since we couldn't modify the library source. font-display: block keeps invisible text during the brief load window, then swaps — better than no declaration at all, and no visible layout shift.

Duplicate Preconnect Hints

The page had 5 <link rel="preconnect"> tags, including two pointing to the same Google Fonts domains. Duplicate preconnect hints waste browser connection budget. We reduced from 5 to 3, keeping only the origins that were actually needed for above-the-fold resources.

WebP Image Conversion

Image payloads were a significant LCP drag. The hero "best seller" image alone was 7KB as a PNG. Small, yes — but PNGs are lossless, and this particular image had simple flat colors and gradients that compress extremely well as WebP.

Image optimization — best-seller.png
# Convert to WebP with quality 80
cwebp -q 80 best-seller.png -o best-seller.webp

# Result:
best-seller.png: 7.2 KB
best-seller.webp: 1.8 KB (75% smaller)

# HTML: picture element for browser compatibility

We implemented <picture> elements with WebP source and JPEG/PNG fallback across product_template.php, mfg_page.php, and productPage.php. Manufacturer logos were the biggest opportunity — many were large PNGs serving a 200px-wide slot.

Before — PNG only
<img src="mfg-logo.png"
  width="200" height="80"
  alt="Manufacturer">
After — WebP with fallback
<picture>
 <source srcset="logo.webp"
  type="image/webp">
 <img src="logo.jpg"
  width="200" height="80"
  alt="Manufacturer">
</picture>

Accessibility: 88 to 92

The accessibility improvements were straightforward but impactful for both score and usability:

  • Form inputs missing aria-labels: Inputs with placeholder attributes but no aria-label or associated <label> are invisible to screen readers. Added aria-label attributes throughout the checkout and search forms.
  • Link color contrast: Navigation links in #999 gray failed WCAG AA contrast ratio against the white background. Changed to #666 and added underlines to links in body text — both fixes improve contrast ratio and visual affordance.
Contrast fix — link color
/* Before — contrast ratio 2.85:1 (fails AA) */
a { color: #999; }

/* After — contrast ratio 5.74:1 (passes AA) */
a { color: #666; text-decoration: underline; }

Final Results

92
Performance
92
Accessibility
100
Best Practices
100
SEO
MetricBeforeAfter
Lighthouse Performance6692
Lighthouse Accessibility8892
Best Practices92100
SEO90100
CLS (lab)1.2480.0
Real User LCP (CrUX)N/A1.3s
Real User INP (CrUX)N/A73ms
Real User CLS (CrUX)N/A0.0
hero image size7KB PNG1.8KB WebP
Preconnect hints5 (2 dupes)3

Why not 100 Performance? We deliberately kept CSS render-blocking to prevent CLS. A non-blocking approach would have scored higher in Lighthouse's lab environment, but would have re-introduced the 1.248 CLS in real-user measurements. A score of 92 with zero real CLS beats 98 with visible layout shift every time.

Tech Stack

PHP 8.1 • Apache • Google Lighthouse 11 • Chrome DevTools • WebP (cwebp) • Chrome User Experience Report (CrUX) • WCAG 2.1 AA • Schema.org

What's Your Lighthouse Score?

We'll run a full Lighthouse + Core Web Vitals audit on your site and tell you exactly what's hurting your score — free.

Get Free Performance Audit View All Case Studies