Why JavaScript Is Your Biggest Performance Risk

Of all the resources a browser downloads, JavaScript is the most expensive — not just to transfer, but to parse and execute. A 300 KB JavaScript file costs far more in CPU time than a 300 KB image. This directly impacts Core Web Vitals: the set of metrics Google uses to measure real-world user experience and factor into search rankings.

The three Core Web Vitals most affected by JavaScript are:

  • LCP (Largest Contentful Paint): Render-blocking scripts delay the main content from appearing.
  • INP (Interaction to Next Paint): Heavy JS execution on the main thread delays responses to user input.
  • CLS (Cumulative Layout Shift): Scripts that inject content without reserved space cause jarring layout shifts.

Technique 1: Use async and defer Attributes

Adding async or defer to your <script> tags prevents render-blocking — one of the quickest wins available.

  • defer: Downloads the script in parallel with HTML parsing and executes it after the DOM is ready. Best for most scripts.
  • async: Downloads in parallel and executes as soon as it's ready — regardless of DOM state. Best for independent scripts like analytics.

Rule of thumb: use defer for everything that doesn't need to run before the page renders.

Technique 2: Code Splitting

Don't ship one giant bundle. Use dynamic import() to split your JavaScript into smaller chunks that load on demand:

// Only load the chart library when the user opens the analytics tab
button.addEventListener('click', async () => {
  const { renderChart } = await import('./chart.js');
  renderChart(data);
});

This keeps your initial page load lean and defers the cost of heavy libraries until they're actually needed.

Technique 3: Serve Libraries from a Public CDN

Loading common libraries (React, Lodash, Chart.js) from a reputable public CDN like jsDelivr or cdnjs offers two benefits:

  • Edge delivery: Files are served from a server geographically close to your user.
  • Browser cache hits: If another site already loaded the same library version from the same CDN, the file may already be in the user's browser cache — zero download needed.

Technique 4: Tree Shaking and Dead Code Elimination

If you're using a bundler like Rollup or esbuild, enable tree shaking to remove unused code from your final bundle. Import only what you need:

// Bad: imports entire Lodash library
import _ from 'lodash';

// Good: imports only the debounce function
import debounce from 'lodash/debounce';

This alone can reduce bundle sizes dramatically for utility-heavy codebases.

Technique 5: Preload Critical Scripts

Use <link rel="preload"> to tell the browser about critical JavaScript files as early as possible, so it can begin downloading them while still parsing HTML:

<link rel="preload" href="/js/app.js" as="script">

Combine this with a CDN URL for maximum effect — the browser discovers and starts fetching your critical JS immediately, from the closest edge node.

Measuring Your Improvements

After applying these techniques, measure the impact using:

  • Chrome DevTools Lighthouse: Run an audit before and after each change.
  • WebPageTest.org: Free, detailed waterfall charts showing exactly when each resource loads.
  • Google Search Console: Monitor Core Web Vitals scores from real users over time.

Summary

JavaScript performance is iterative. Start with defer/async attributes for immediate wins, then progressively introduce code splitting, CDN delivery, and tree shaking as your project matures. Each technique compounds on the others — and your users (and search rankings) will notice the difference.