Audit Results
https://woocommerce.com/
WordPressLighthouse Lab Data
Measured in a simulated environment. Values may differ from real user experience.Field Data — Mobile (Real Users)
Core Web Vitals PoorChrome UX Report — p75 values from real mobile user experiences over the last 28 days
Summary
WooCommerce.com has all four CrUX field metrics in the "needs-improvement" (orange) zone — LCP at 2216ms, INP at 153ms, TTFB at 903ms, and FCP at 2022ms. The root causes are: (1) massive GTM container bloat with 32 paused tags, 95 unused variables, and duplicate trackers across GTM and inline code, (2) uncached HTML responses despite being a WordPress marketing page, (3) 9 render-blocking CSS files including full Font Awesome, and (4) 60+ images loaded eagerly on a single page. Lab TBT (1362ms) is much worse than field INP (153ms), suggesting real users have faster devices than Lighthouse's 4x CPU throttle simulates, but the orange metrics still indicate clear room for improvement.
- 1Eliminate Duplicate Trackers — GA4, Facebook Pixel, Hotjar, and LinkedIn Are All Loaded Twice
- 2Delete 32 Paused GTM Tags — They Still Add to Bundle Size and Main Thread Parsing
- 3Reduce TTFB — WordPress HTML is Not Edge-Cached
- 4Defer All Analytics and Third-Party Script Loading by 3 Seconds
- 5Replace Font Awesome with Inline SVG — 37.5KB Render-Blocking CSS + Webfont Bloat
- —Delete 95 Unused GTM Variables and 53 Orphaned Triggers
- —Convert Hero and Showcase Images from JPG/PNG to WebP with Responsive Srcset
- —Use Facade Pattern for Wistia Video Embed
- —Lazy-Load 50+ Below-the-Fold Images — Currently All Load Eagerly
LCP: 2216ms → ~1500ms (from TTFB caching ~600ms + CSS consolidation ~200ms + Font Awesome removal ~200ms), INP: 153ms → ~100ms (from duplicate tracker removal + GTM cleanup + script deferral), CLS: 0.00 → 0.00 (no CLS issues detected), TTFB: 903ms → ~250ms (from full-page caching + CDN edge cache)
Recommendations
Eliminate Duplicate Trackers — GA4, Facebook Pixel, Hotjar, and LinkedIn Are All Loaded Twice
document. The duplicates detected:
- Google Analytics 4 (G-98K30SHWB2): inline gtag.js (167.8KB) + 9 GA4 tags in GTM
- Facebook Pixel: inline fbevents.js (96.9KB) + 7 FB Pixel tags in GTM (SDK + event tags)
- Hotjar (278703): inline hotjar-278703.js (7KB) + Custom HTML tag in GTM
- LinkedIn Insight: inline insight.min.js (18.7KB) + 6 LinkedIn pixel tags in GTM
Total wasted: ~290KB of duplicate JS plus doubled main thread execution. Each duplicate analytics script installs event listeners on document — every user click passes through all of them, directly degrading INP.
Action: Keep trackers only in GTM for centralized management. Remove the inline implementations from your WordPress theme/plugin: header.php or a plugin settings page for gtag/js?id=G-98K30SHWB2. The GTM container already has a Google Tag and GA4 Event tags for this measurement ID.connect.facebook.net/en_US/fbevents.js. Note: most FB Pixel GTM tags are paused (see GTM cleanup recommendation), so re-enable the needed ones in GTM first.hotjar-278703. The GTM tag Hotjar (loading static.hotjar.com/hotjar-) is already enabled.snap.licdn.com/li.lms-analytics/insight.min.js. Note: all 6 LinkedIn GTM tags are paused — keep one enabled in GTM before removing inline code.Delete 32 Paused GTM Tags — They Still Add to Bundle Size and Main Thread Parsing
px.ads.linkedin.com) — likely from old campaigns with different conversion events
- 3 Facebook Pixel event tags (AddToCart, InitiateCheckout, Purchase) — plus the FB SDK tag and 3 unidentifiable Lead tags
- 3 TradeDesk/adsrvr.org tags — ad platform tracking
- 2 Google Ads Conversion tags and 1 Remarketing tag (Conv ID: 878432221)
- 3 GA4 Event tags — duplicate config/event tags
- 2 Custom Tags (__cvt_5RM3Q) — standard and custom event variants
- 1 Custom Tag (__bzi) — unknown vendor
- 12 unidentifiable Custom HTML tags — paused with stripped code
- 3 unidentifiable Facebook Pixel — Lead tags
Steps: px.ads.linkedin.com/collect — there are 6 separate tags that likely tracked different conversion events for old campaignsDefer All Analytics and Third-Party Script Loading by 3 Seconds
<head>. This removes analytics from the critical rendering path entirely, freeing bandwidth and main thread for LCP resources (CSS, hero image).<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-W64W8Q');</script><script>window.addEventListener('load', function() {
setTimeout(function() {
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src='https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);})(window,document,'script','dataLayer','GTM-W64W8Q');
}, 3000);
});</script>Replace Font Awesome with Inline SVG — 37.5KB Render-Blocking CSS + Webfont Bloat
fa- classes in the DOM<i class="fa-..."> elements with inline <svg> elements for above-the-fold icons (logo area, nav)functions.php:
wp_dequeue_style('font-awesome'); (or the handle used by your theme)<link rel="stylesheet" href="/wp-content/themes/woo/fonts/font-awesome/css/all.min.css?v=1.0"> <i class="fab fa-wordpress"></i>
<svg width="24" height="24" viewBox="0 0 512 512" aria-hidden="true"><path fill="currentColor" d="M256 8C119..."/></svg>
Reduce TTFB — WordPress HTML is Not Edge-Cached
server: Kestrel with no visible caching headers (no Cache-Control, no X-Cache, no CDN cache status).
This means every visitor request goes directly to the origin WordPress server — no edge caching of HTML. For a marketing homepage that changes at most a few times per week, this is unnecessary. Every request triggers PHP execution, database queries, and Liquid template rendering.
Steps: stale-while-revalidate for seamless revalidation.curl -w 'TTFB: %{time_starttransfer}s\n' -o /dev/null -s https://woocommerce.com/ — should be <300ms after caching is active.# No caching headers on HTML response server: Kestrel
# nginx configuration for WordPress full-page cache
location / {
# WP Rocket cache file path
set $cache_uri $request_uri;
# Skip cache for logged-in users, POST requests, query strings
if ($request_method = POST) { set $cache_uri 'null'; }
if ($query_string != '') { set $cache_uri 'null'; }
if ($http_cookie ~* "wordpress_logged_in") { set $cache_uri 'null'; }
# Serve cached HTML file directly
try_files /wp-content/cache/wp-rocket/$http_host$cache_uri/index-https.html $uri $uri/ /index.php$is_args$args;
# Cache-Control for CDN edge caching
add_header Cache-Control "public, max-age=300, stale-while-revalidate=3600";
}Consolidate 9 Render-Blocking CSS Files into 2-3 Combined Files
_static/??-eJx... (1.4KB + 19.3KB + 30.3KB) — WordPress static file concatenation
- cover/style.min.css (1.9KB), gallery/style.min.css (2.1KB) — Gutenberg block styles
- frontend.css (508B) — product vendors plugin
- Another concatenated bundle (1.8KB)
- deferred-global.min.css (3.7KB) — theme CSS
Many of these are tiny files (<2KB) that could be inlined or combined.
Steps: functions.php:frontend.css (508B) from product-vendors plugin is half a kilobyte — should be inlined directly into the combined stylesheet or dequeued if not needed on the homepage.<!-- 9 separate render-blocking CSS files --> <link rel="stylesheet" href="/wp-content/themes/woo/fonts/font-awesome/css/all.min.css"> <link rel="stylesheet" href="/_static/??-eJx9i0EK..."> <link rel="stylesheet" href="/wp-includes/blocks/cover/style.min.css"> <link rel="stylesheet" href="/wp-includes/blocks/gallery/style.min.css"> <link rel="stylesheet" href="/_static/??-eJyVjM0K..."> <link rel="stylesheet" href="/wp-content/plugins/wccom-product-vendors/assets/frontend.css"> <link rel="stylesheet" href="/_static/??-eJytj0EO..."> <link rel="stylesheet" href="/wp-content/themes/woo/dist/deferred-global.min.css"> <link rel="stylesheet" href="/_static/??-eJyVjEsO...">
<!-- Dequeue unused block styles in functions.php -->
<?php
add_action('wp_enqueue_scripts', function() {
// Remove block styles not used on homepage
if (is_front_page()) {
wp_dequeue_style('wp-block-cover');
wp_dequeue_style('wp-block-gallery');
wp_dequeue_style('wccom-product-vendors-frontend');
}
}, 100);
?>Move Hotjar and Klaviyo Custom HTML Tags to Native GTM Templates with Delayed Triggers
static.hotjar.com/hotjar- via Custom HTML. Hotjar installs event listeners on document for session recording, adding 200-400ms of main thread blocking and capturing every user click.
- Klaviyo — loads static.klaviyo.com/klaviyo.js via Custom HTML. Email marketing widget that doesn't need to load until the user scrolls or after page load.
- Custom HTML (woo.kliken.com/success) — loads Kliken marketing script eagerly.
Custom HTML tags run synchronously in the main thread and are heavier than native GTM template equivalents. Native GTM templates are sandboxed, load asynchronously, and are optimized by GTM's execution engine.
Steps: Delete 95 Unused GTM Variables and 53 Orphaned Triggers
Convert Hero and Showcase Images from JPG/PNG to WebP with Responsive Srcset
@2x variants regardless of device width:
- woo-header-01-C@2x.jpg (153.3KB, LCP element) — the grüum store screenshot
- woo-header-01-A@2x.jpg (186KB) — another header variant
- Showcase PNGs: woo-block-theme-5@2x.png (280.7KB), coolhunter-1.png (372.7KB), atholl.png (310.3KB), lunch-1.png (305.5KB) — all far too large
WebP typically provides 25-35% savings over JPG and 50-70% savings over PNG at equivalent quality.
Steps: <picture> elements or WordPress plugin's WebP delivery for automatic format negotiationsrcset to hero images so mobile devices don't download @2x desktop sizes<img src="/wp-content/uploads/2025/01/woo-header-01-C@2x.jpg" alt="gruum store">
<picture>
<source type="image/webp"
srcset="/wp-content/uploads/2025/01/woo-header-01-C-400w.webp 400w,
/wp-content/uploads/2025/01/woo-header-01-C-768w.webp 768w,
/wp-content/uploads/2025/01/woo-header-01-C@2x.webp 1536w"
sizes="(max-width: 768px) 100vw, 768px">
<img src="/wp-content/uploads/2025/01/woo-header-01-C@2x.jpg"
alt="gruum store" width="768" height="614">
</picture>Use Facade Pattern for Wistia Video Embed
fast.wistia.com/embed/medias/7i19o3jndu/swatch) that takes 4132ms to fully load on mobile — including 3029ms just for connection setup to a new third-party domain. The full Wistia player loads additional JS, CSS, and video assets that total hundreds of KB. This video appears below the fold but still competes for network bandwidth during page load.
Steps: <script src="https://fast.wistia.com/embed/medias/7i19o3jndu.jsonp" async></script> <script src="https://fast.wistia.com/assets/external/E-v1.js" async></script> <div class="wistia_embed wistia_async_7i19o3jndu"></div>
<div class="wistia-facade" style="position:relative;cursor:pointer;background:url('/wp-content/uploads/wistia-thumb.webp') center/cover;aspect-ratio:16/9">
<button aria-label="Play video" style="position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:rgba(0,0,0,.3);border:none">
<svg width="68" height="48" viewBox="0 0 68 48"><path d="M66.5 7.7s-.7-4.7-2.8-6.8C60.7-2 57.2-2 55.6-2.2 46.4-3 34-3 34-3s-12.4 0-21.6.8C10.8-2 7.3-2 4.3.9 2.2 3 1.5 7.7 1.5 7.7S.8 13.3.8 18.8v5.2c0 5.5.7 11 .7 11s.7 4.7 2.8 6.8c3 3 7 2.9 8.8 3.2C19.4 45.5 34 45.6 34 45.6s12.4 0 21.6-.8c1.6-.2 5.1-.2 8.1-3.1 2.1-2.1 2.8-6.8 2.8-6.8s.7-5.5.7-11v-5.2c0-5.5-.7-11.1-.7-11.1z" fill="red"/><path d="M27.2 13.5l18.1 10.5-18.1 10.5z" fill="#fff"/></svg>
</button>
</div>
<script>
document.querySelector('.wistia-facade').addEventListener('click', function() {
var s1 = document.createElement('script');
s1.src = 'https://fast.wistia.com/embed/medias/7i19o3jndu.jsonp';
var s2 = document.createElement('script');
s2.src = 'https://fast.wistia.com/assets/external/E-v1.js';
this.innerHTML = '<div class="wistia_embed wistia_async_7i19o3jndu" style="width:100%;aspect-ratio:16/9"></div>';
document.head.appendChild(s1);
document.head.appendChild(s2);
}, {once: true});
</script>