All fix guides
WarningPerformance & Core Web Vitals

How to Reduce Unused JavaScript

Your page ships JavaScript the browser downloads and parses but often never runs on that page. This wastes bandwidth and main-thread time, delaying LCP and interactivity. The fix is to load less JS up front: code-split, defer, tree-shake, and remove dead scripts and unused third-party tags.

What this means

The excess_unused_js warning means the audit found JavaScript being delivered to the page that isn't needed to render what the visitor actually sees, at least not immediately. Chrome's coverage tooling (the same data Lighthouse uses) measures how many bytes of each script are executed versus downloaded. When a large share of your bundled or third-party JS goes unused on load, it gets flagged.

Common culprits: one large bundle that includes code for every page of the site (no code-splitting); third-party tags like chat widgets, A/B testing tools, analytics, and ad scripts that load site-wide; unused portions of libraries (importing an entire utility library to use one function); and plugins or theme features you no longer use.

The cost isn't just the download. Every kilobyte of JavaScript has to be parsed and compiled on the browser's main thread, which is far more expensive per byte than an image. On mid-range mobile devices, this parse-and-compile work is often what delays the page becoming interactive.

Why it matters

Unused JavaScript hurts Core Web Vitals, which are part of Google's page experience ranking signals. Large scripts delay the browser from painting your Largest Contentful Paint (LCP) element, and heavy main-thread work worsens Interaction to Next Paint (INP). Google's "good" thresholds are LCP under 2.5 seconds and INP under 200 milliseconds at the 75th percentile, and excess JS is a common reason pages miss them on mobile.

Beyond the ranking signal, script-heavy pages raise bounce rate and cut conversions. The visitor stares at a blank or unresponsive screen while the CPU chews through code they may never trigger.

There's an AI-visibility angle too. Many AI crawlers execute little or no JavaScript. If your core content only appears after a heavy bundle runs, a non-rendering crawler can see an empty shell. Serving content in the initial HTML (server-side rendering or static generation) makes it reliably visible to Googlebot and to answer engines like Google's AI Overviews, ChatGPT, and Perplexity. Trimming bundles and moving toward server-rendered content helps you on both fronts at once.

How to fix it

  1. 1

    Find exactly which scripts are unused

    Open Chrome DevTools, press Ctrl+Shift+P (Cmd+Shift+P on Mac), type "Coverage," and choose "Show Coverage." Reload the page and read the bars: the red portion is unused bytes. Sort by URL to see which bundles and third-party tags waste the most, so you focus instead of guessing. Cross-check against the "Reduce unused JavaScript" item in a Lighthouse or PageSpeed Insights run, which lists the same files with estimated savings.

  2. 2

    Defer non-critical JavaScript

    Any script not needed to paint above-the-fold content should not block rendering. Add the defer attribute to your own scripts so they download in parallel and run after HTML parsing. Use async for independent third-party tags like analytics. Load tags that only matter after interaction (chat, surveys, some ad scripts) on first user interaction or when the page is idle. This doesn't remove the bytes, but it stops them from delaying LCP.

  3. 3

    Code-split so each page loads only its own JS

    The biggest single win on most sites is splitting one giant bundle into per-route chunks, so a visitor to a blog post doesn't download the checkout code. Modern bundlers (webpack, Vite, esbuild, Rollup) do this with dynamic import(). Frameworks like Next.js, Nuxt, and Remix code-split by route automatically and support lazy loading for heavy components (charts, maps, video players) that aren't visible on load. Pair this with tree-shaking: import only the functions you use, for example import debounce from 'lodash/debounce' instead of the whole library.

  4. 4

    Audit and cut third-party tags

    Third-party scripts are often the largest chunk of unused JS, and you don't control their internals, so the lever is deciding whether each tag earns its place. In Google Tag Manager or your tag setup, remove tags you no longer use and set triggers so tags fire only on pages that need them rather than site-wide. Load a chat widget or heatmap tool only where it matters or on first interaction. Each removed vendor script removes its full download, parse, and execution cost.

  5. 5

    On WordPress, dequeue per-page plugin assets and defer via a caching plugin

    Many WordPress plugins enqueue their JavaScript on every page even when the feature appears on only one. Use Perfmatters or Asset CleanUp to dequeue scripts on pages that don't use them (for example, disable a contact-form script everywhere except the contact page). A caching or optimization plugin such as WP Rocket, LiteSpeed Cache, or the free FlyingPress can defer JavaScript and delay non-critical scripts until interaction. Delete plugins you've deactivated but left installed, and prefer lightweight themes that don't load a page builder's full runtime everywhere.

  6. 6

    Re-test and confirm the savings

    After each change, re-run PageSpeed Insights and re-check the Coverage tab to confirm unused bytes dropped and LCP and INP improved. Test on a throttled mobile profile, since that's where excess JS hurts most and how Google's field data is weighted. Deferring or removing a script can break functionality, so click through interactive elements (menus, forms, sliders) before shipping. Lab tools confirm the fix immediately; the Core Web Vitals report in Search Console uses a rolling 28-day window, so field metrics catch up over time.

Example

<!-- BEFORE: blocking, ships on every page, runs before the page can render -->
<script src="/js/app.bundle.js"></script>

<!-- AFTER: defer non-critical JS so it doesn't delay LCP -->
<script src="/js/app.bundle.js" defer></script>
<script src="https://www.googletagmanager.com/gtag/js?id=GA_ID" async></script>

<!-- Code-split: load a heavy widget only when it enters the viewport -->
<div id="map" data-map></div>
<script type="module">
  const el = document.querySelector('[data-map]');
  const io = new IntersectionObserver(async (entries) => {
    if (entries[0].isIntersecting) {
      const { initMap } = await import('/js/map.js'); // separate chunk
      initMap(el);
      io.disconnect();
    }
  });
  io.observe(el);
</script>

Defer your own scripts and lazy-load a heavy component only when it's needed, instead of shipping everything in one blocking bundle.

Platform-specific steps

WordPress (Yoast / Rank Math)

Yoast and Rank Math handle metadata, not asset loading, so they won't fix this directly. Add Perfmatters or Asset CleanUp to dequeue plugin scripts on pages that don't use them, and use WP Rocket, LiteSpeed Cache, or FlyingPress to defer JavaScript and delay non-critical scripts until user interaction. Delete deactivated plugins and switch off theme or page-builder features you don't use.

Shopify

Audit installed apps first: many inject JavaScript on every page even after you stop using them, so uninstall dead apps rather than just disabling them. Move third-party pixels and tags into Shopify's Custom Pixels (Settings > Customer events) so they run in a sandbox instead of on the main thread. Choose a lightweight Online Store 2.0 theme and remove unused theme app embeds under Themes > Customize > App embeds.

Wix

Wix controls its own bundling, so your main levers are removing unused third-party embeds and apps from the Wix App Market and avoiding heavy custom Velo code that runs on every page. Remove tracking tags you no longer need under Settings > Custom Code, and scope any remaining ones to specific pages rather than the whole site.

Squarespace

Squarespace bundles its platform JavaScript for you, so focus on what you add: remove unused code injections (Settings > Advanced > Code Injection), delete third-party embed blocks and tracking scripts you no longer use, and scope injected scripts to the specific pages that need them via page-level code injection rather than site-wide.

Next.js / raw HTML

Next.js code-splits by route automatically; use next/dynamic with { ssr: false } for heavy client-only components (charts, editors, maps) so they load in a separate chunk on demand. Import specific functions instead of whole libraries to enable tree-shaking, run @next/bundle-analyzer to find bloated chunks, and load third-party tags with next/script using the afterInteractive or lazyOnload strategy. For raw HTML, split bundles with dynamic import() and add defer or async to script tags.

Free tool
Check this with the LCP/CLS Culprit Finder

Frequently asked

What counts as "too much" unused JavaScript?

There's no fixed byte limit. Lighthouse flags a file when it estimates you could save a meaningful amount of load time by not sending its unused portion, roughly starting around 20 KB of wasted transfer per file. Focus less on the exact number and more on the biggest offenders in the Coverage report and on whether your LCP is under 2.5 seconds on mobile. If it is, small amounts of unused JS aren't worth chasing.

Will deferring JavaScript break my site?

It can if a script depends on running before the DOM finishes parsing or before another script. defer preserves execution order among deferred scripts and runs them after parsing, which is safe for most site scripts. async runs as soon as it downloads, so use it only for independent tags like analytics. Test interactive features after each change; if something breaks, that specific script probably needs to stay non-deferred or load differently.

Is unused JavaScript the same as render-blocking JavaScript?

They overlap but aren't identical. Render-blocking JavaScript is a script in the <head> without defer or async that stops the browser from rendering until it loads and runs. Unused JavaScript is code that's downloaded but never executed on that page, whether or not it blocks rendering. A script can be both, one, or neither. Deferring fixes the render-blocking part; code-splitting and removing tags fixes the unused part.

Does reducing JavaScript help with AI search and ChatGPT visibility?

Indirectly, yes. Many AI crawlers execute little or no JavaScript, so if your main content only renders after a heavy bundle runs, those crawlers may see an empty page. Serving content in the initial HTML through server-side rendering or static generation makes it reliably visible to AI answer engines and to Googlebot. Trimming unused JS is a natural byproduct of moving toward server-rendered content.

I use a page builder like Elementor or Divi. Why is my unused JS so high?

Page builders load a large runtime and multiple widget scripts on every page, much of which a given page never uses. Enable the builder's own performance features (in Elementor, "Improved Asset Loading" and "Optimized DOM Output"), use an asset-management plugin to dequeue unused widget scripts per page, and defer scripts through a caching plugin. For pages that don't need the builder at all, consider building them with the native block editor.

Does your site have this issue?

Run a free, AI-powered audit and we’ll flag this and 150+ other checks in about a minute. No signup.

No signup needed. Results in under 60 seconds.

Related fixes