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:
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.
family=Inter&display=swap
/* FontAwesome — no font-display */
@font-face {
src: url(fa.woff2);
/* missing font-display! */
}
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.
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.
width="200" height="80"
alt="Manufacturer">
<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
placeholderattributes but noaria-labelor associated<label>are invisible to screen readers. Addedaria-labelattributes throughout the checkout and search forms. - Link color contrast: Navigation links in
#999gray failed WCAG AA contrast ratio against the white background. Changed to#666and added underlines to links in body text — both fixes improve contrast ratio and visual affordance.
a { color: #999; }
/* After — contrast ratio 5.74:1 (passes AA) */
a { color: #666; text-decoration: underline; }
Final Results
| Metric | Before | After |
|---|---|---|
| Lighthouse Performance | 66 | 92 |
| Lighthouse Accessibility | 88 | 92 |
| Best Practices | 92 | 100 |
| SEO | 90 | 100 |
| CLS (lab) | 1.248 | 0.0 |
| Real User LCP (CrUX) | N/A | 1.3s |
| Real User INP (CrUX) | N/A | 73ms |
| Real User CLS (CrUX) | N/A | 0.0 |
| hero image size | 7KB PNG | 1.8KB WebP |
| Preconnect hints | 5 (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