At a glance
JavaScript bytes downloaded but not executed during the page load, ranked per script. Lighthouse’s Coverage analysis identifies which lines of JS code never ran during the audit; the bytes occupied by those lines are “unused JS”. Brands routinely ship 50-70 percent of their JavaScript bundle on every page, with only 30-50 percent actually executing. The unused bytes still cost: download time, parse time, and memory. Removing them via code-splitting and tree-shaking is a structural performance improvement that compounds across every page-view.
| What it counts | Per JS resource: total bytes downloaded, bytes covered (executed), bytes wasted (downloaded but never run). Lighthouse uses Chrome’s Coverage API to instrument which lines fired during the audit; the difference between downloaded and covered is “unused”. |
| Sample type | Lab data from a single Lighthouse audit. Reflects what didn’t execute during the synthetic page load; pages with rich user interaction (filter widgets, modals, dynamic content) may execute more JS in real use than lab measures. |
| Why bundles accumulate unused code | (1) Frameworks and libraries ship more features than any single page uses: jQuery includes selectors, animations, AJAX, deferred utilities; a homepage may use 20 percent of jQuery. (2) Polyfills for older browsers ship even to modern browsers that don’t need them. (3) Dead code from removed features stays in the bundle if not actively cleaned. (4) Per-page features bundled site-wide: cart-drawer JS shipped on every page including pages without cart action. (5) Third-party scripts ship full SDKs when only specific functionality is used. |
| Common ecommerce patterns | (1) jQuery on Stencil sites: 84KB total, ~64KB unused per page typically. (2) GTM container: 56-200KB depending on tag count, much unused per page-load. (3) Klaviyo SDK: 180KB total, only popup + behavior tracking active on most pages (~80-100KB unused). (4) A/B testing tools: full variant fetcher loaded even on pages without active tests. |
| Optimisation playbook | (1) Code-splitting: webpack/Rollup/esbuild can split a single bundle into per-page chunks; each page only loads its own JS. Estimated savings: 30-60 percent of JS bytes. (2) Tree-shaking: removes unused exports automatically with modern bundlers. Requires ES module imports; cuts library size 20-40 percent. (3) Dynamic imports: load heavy widgets (modal libraries, chart libraries) only when triggered. (4) Defer non-critical bundles: even unused-on-this-page JS doesn’t need to block render. (5) Library swaps: replace heavy libraries with lighter alternatives (Moment.js → date-fns saves ~80KB; jQuery → vanilla JS or modern alternatives saves 84KB). |
| The diminishing-return curve | First 50 percent of unused JS is usually trivial to remove via code-splitting. Next 30 percent requires more work (auditing third-party SDKs, lazy-loading widgets). Last 20 percent is structural (rewriting the bundling architecture). Most merchants stop at 60-70 percent removal; the marginal effort beyond that exceeds marginal benefit. |
| Currency | n/a, this is a list of byte-savings opportunities. |
| Time window | T (current state). |
| Alert trigger | top entry > 100KB unused (red), > 50KB unused (amber). |
| Sentiment key | null |
| Roles | owner, operations |
Calculation
Calculated automatically from your Website Performance (PageSpeed + CrUX) data. See the At a glance summary above for what the metric tracks and the worked example below for a typical reading.Worked example
A UK-based BigCommerce fashion store homepage audit, mobile, Wednesday 15 May 26.| Rank | Resource | Total bytes | Used bytes | Unused | Coverage % | Recommendation |
|---|---|---|---|---|---|---|
| 1 | gtm.js (GTM container) | 184KB | 42KB | 142KB | 23% | Quarterly GTM audit; remove unused tags |
| 2 | klaviyo-sdk.js | 178KB | 64KB | 114KB | 36% | Lazy-load popup component |
| 3 | tidio-chat.js | 148KB | 38KB | 110KB | 26% | Defer entirely to post-load |
| 4 | jquery-3.6.0.min.js | 84KB | 22KB | 62KB | 26% | Long-term: replace with vanilla JS for new code |
| 5 | stencil-bundle.js (theme JS) | 132KB | 78KB | 54KB | 59% | Code-split by template type |
| 6 | optimizely.min.js | 96KB | 28KB | 68KB | 29% | Lazy-load only when test is active for the page |
| 7 | hotjar-recording.js | 84KB | 24KB | 60KB | 29% | Sample 10% of sessions instead of 100% |
| 8 | meta-pixel-base.js | 32KB | 18KB | 14KB | 56% | Acceptable; conversion tracking has thin profile |
| Total JS shipped | 938KB | 314KB | 624KB | 33% used |
- 66 percent of all JavaScript shipped to the homepage is unused. That’s 624KB of bytes the user downloads, the browser parses, and the device’s memory holds, for code that never runs. For a typical mid-tier mobile device on slow 4G, those 624KB cost roughly 3 seconds of total processing (download + parse + execute-skipped) over the page lifecycle.
- GTM at 142KB unused (rank 1) is the most addressable single item. The GTM container ships every tag’s setup code regardless of whether the tag fires on the current page. Quarterly tag audit + page-scoped triggers typically reduces GTM bundle size by 40-60 percent. Time investment: 3-5 hours of GTM workspace cleanup.
- Klaviyo SDK at 114KB unused (rank 2) is partly structural (Klaviyo ships unified SDK with popup + behaviour + product feed components), partly addressable. Lazy-loading the popup component until the trigger fires saves 60-80KB; behaviour tracking still runs at page-load.
- Tidio chat at 110KB unused (rank 3) can be deferred entirely to post-load. The chat widget doesn’t need to load before first paint; users who don’t engage with chat (90 percent of sessions) never need the JS at all.
-
jQuery at 62KB unused (rank 4) is structural to Stencil themes. Short-term: defer (covered in
psi_render_blocking). Long-term: write new theme code in vanilla JS or modern alternatives; eventually remove jQuery dependency entirely. Multi-month project, not a quick win. - Stencil theme bundle at 54KB unused (rank 5) is the cleanest code-split target. Modern bundlers can produce per-template chunks: homepage-only JS, PDP-only JS, collection-only JS. Each template loads only its own bundle plus shared core. Code-splitting effort: 2-4 weeks for a developer familiar with Webpack/esbuild + theme architecture; ongoing benefit compounds.
- Optimizely (rank 6) ships the full A/B test variant fetcher even on pages without active tests. Configure to lazy-load only when an active test targets the current page. Saves 60-70KB on most pages.
- Cumulative impact estimation: removing or deferring the top 7 entries cuts JS bytes by 400-500KB and parse time by 800-1,500ms. Mobile p75 INP drops by 100-300ms; mobile p75 LCP drops by 200-500ms (less direct, but JS competes with image rendering for main-thread time).
- Read top 5 entries. Concentrate effort on the largest unused-byte items.
- Categorise per item: third-party (defer or remove), first-party theme (code-split), framework (long-term replacement).
- Verify in DevTools Coverage tab: confirms the unused-byte estimates and shows exactly which lines never ran.
- Ship in priority order: third-party deferral first (fastest wins), then code-splitting (medium effort, large benefit), then library replacements (longest, structural).
| Time horizon | Action |
|---|---|
| First 1 hour | Identify top 3 entries; verify Coverage in DevTools. |
| First 24 hours | Defer or remove dead third-party scripts. |
| First 7 days | GTM tag audit; lazy-load chat + popup. |
| First 14 days | Code-split first-party theme bundle. |
| First 30 days | Re-audit; measure cumulative impact. |
Sibling cards merchants should reference together
| Card | Why merchants reach for it |
|---|---|
psi_unused_css | Same pattern for CSS; tree-shaking and code-splitting apply equivalently. |
psi_third_party_cost | Third-party scripts dominate unused-JS rankings on most ecommerce sites. |
psi_render_blocking | Render-blocking + unused-JS often overlap; deferring synchronous JS helps both. |
psi_js_execution | Total JS execution time; correlates with unused JS plus active execution. |
psi_total_weight | JS bytes contribute to total weight. |
crux_inp_p75 | INP heavily affected by JS bundle size and parse time. |
crux_lcp_p75 | LCP affected when JS competes with image rendering. |
psi_top_opportunities_ms | Unused-JS typically ranks top 5 in opportunity list. |
psi_perf_score_summary | Composite score; JS removal lifts TBT score (30 percent of weight). |
Reconciling against the vendor’s own dashboard
Where to look:- PageSpeed Insights, “Reduce unused JavaScript” opportunity in the Diagnostics section.
- Chrome DevTools → Coverage tab, line-level visibility of which JS lines executed; the most useful diagnostic tool.
- WebPack Bundle Analyzer (or equivalent for esbuild/Vite), shows what’s in your bundle and where the bytes go.
| Reason | Direction | What to do |
|---|---|---|
| Audit vs interaction. Lighthouse measures unused JS during the synthetic page-load; pages with rich interactivity may execute more JS in real use. | Vortex IQ may over-report for interactive pages | Use DevTools Coverage with manual interaction for a fairer measurement. |
| Source maps not available. Without source maps, Lighthouse reports unused bytes by raw bundle position; with source maps it can identify specific functions. | n/a (still measures correctly) | Source maps improve diagnostic clarity, not measurement accuracy. |
psi_third_party_cost, crux_inp_p75, and psi_total_weight.
Quick rule for support tickets: if a merchant says “Lighthouse says I have 624KB of unused JS but my bundle is only 380KB total”, the most common cause is that some bundles have grown over multiple deploys without proper invalidation; the merchant is looking at a single bundle while Lighthouse sees the cumulative bytes across all loaded scripts. Sum across all script tags in DevTools Network for direct comparison.
Known limitations / merchant FAQs
Why does my bundle have 60 percent unused JS? Modern frameworks and libraries are designed to ship many features; any single page typically uses a small subset. Plus: legacy code accumulates, polyfills target browsers you no longer care about, dead features get bundled even when not used. The 60 percent figure is industry-typical, not a sign of unusual mismanagement. Code-splitting and tree-shaking are the standard fixes. Will removing unused JS break my site? If done correctly, no. Modern bundlers track imports/exports and tree-shake automatically; the unused code never makes it into the bundle. Manual code removal (deleting dead files) requires more care; verify with your test suite plus a 14-day staged deploy. Can I just defer all my JS? Technically yes, but conversion-critical scripts must run for the page to function. Deferring is for non-critical: chat widgets, session recording, A/B testing, analytics tags. Don’t defer: payment SDKs (must be ready when checkout fires), authentication scripts, scripts that handle initial-render content. Why does Klaviyo SDK show as 64 percent unused? Klaviyo ships a unified SDK with popup + behaviour tracking + product feeds + signup forms + opt-in management. Most pages use only behaviour tracking; the rest is dead weight per page-load. Lazy-loading the popup component is Klaviyo’s recommended optimisation; the SDK is configurable. Should I switch from jQuery to vanilla JS? Long-term, yes. Modern browsers support all jQuery’s core features natively (querySelector, fetch, addEventListener); jQuery added value 10 years ago when browser inconsistencies were common. Migration is gradual: write new code in vanilla JS, leave existing jQuery code in place, retire jQuery when the codebase is mostly migrated. Saves 84KB of bundle size when complete.
Can Vortex IQ refactor my JS for me?
Read-only by design. Vortex IQ identifies unused JS; the merchant’s developer or agency does the bundle work.
My BC site uses Catalyst. Does this still apply?
Yes, in different proportions. Catalyst sites use Next.js with built-in code-splitting; first-party JS unused bytes are typically 10-20 percent (vs 30-40 percent for Stencil). Third-party JS unused proportions are similar. The optimisation effort shifts: Catalyst sites focus on third-party deferral; Stencil sites focus on first-party code-splitting plus third-party deferral.
Is server-side rendering the answer?
Helps but isn’t a complete fix. SSR delivers fully-rendered HTML to the user, removing the need for JS to render initial content. Unused JS still ships and parses; it just doesn’t block the first paint. SSR’s main benefit is LCP improvement; unused-JS reduction requires the same code-splitting work on top.
Should I optimise unused JS or unused CSS first?
JS first, almost always. JS has higher bytes-per-page on most ecommerce sites (200-1000KB vs 50-200KB for CSS), and JS execution cost is more variable across devices than CSS. Unused JS impacts INP heavily; unused CSS mainly affects parse time which is smaller.