All articles · Performance

Where Page Weight Hides: Auditing Images, JavaScript and Fonts

Cover with the article title next to a balance scale with boxes on one side, in amber on a dark background

Page weight is the least glamorous performance metric. It isn’t a Core Web Vital, and a heavy page can still score well on a fast connection. But bytes cost something on every visit: they compete for bandwidth with the content that matters, they cost money for visitors on metered mobile plans, and in the case of JavaScript, they cost CPU time long after the download finishes.

The good news is that weight is easy to measure and usually concentrated in a few places. Most heavy pages are heavy for three or four reasons, not three hundred. This article is a repeatable audit: how to measure the page properly, where to look first, and what to do with each kind of file.

Measure the right numbers

Open the page in Chrome with DevTools on the Network panel, check “Disable cache”, and reload. Then scroll to the bottom and wait, because lazy-loaded images and late scripts are part of the real cost. The summary bar at the bottom shows two totals:

  • Transferred: bytes that crossed the network, after compression. This is what affects load time and data plans.
  • Resources: the decoded size, after decompression. For JavaScript and CSS, this is what the browser has to parse and run.

Both matter. A JavaScript bundle that transfers modestly thanks to Brotli can still be several times larger once decompressed, and the parse and execution cost follows the decoded size.

Next, sort the request list by size and use the type filters (Doc, CSS, JS, Font, Img, Media) to see totals per category. Export a HAR file if you want to compare before and after, or to share the findings with someone else.

Run the audit on more than one template. A home page, a category or listing page and a detail page often have very different weight profiles. Our page weight guide describes how the total is assessed in a scan.

Images: usually the biggest share

On content-heavy sites, images tend to dominate the transferred bytes. The common causes are easy to confirm one by one.

Oversized dimensions. In the Elements panel, hover an image to see its rendered size and its intrinsic size. A photo displayed at 400 pixels wide but delivered at 3,000 is mostly wasted bytes. The fix is a srcset with several widths and a sizes attribute that matches your layout:

<img
  src="/img/team-800.jpg"
  srcset="/img/team-400.jpg 400w, /img/team-800.jpg 800w, /img/team-1600.jpg 1600w"
  sizes="(max-width: 600px) 100vw, 50vw"
  width="1600"
  height="900"
  loading="lazy"
  alt="The support team at the office"
/>

Old formats. Photos as PNG and screenshots as JPEG are both common mismatches. For photos, AVIF and WebP usually produce much smaller files than JPEG at similar quality. For flat graphics and logos, SVG or a well-compressed PNG or WebP often beats a photo format.

Export quality. Images exported straight from a design tool at maximum quality carry detail nobody sees. Compare lower quality settings at the size users actually view them.

Animated GIFs. A GIF animation is often far larger than the same clip as an MP4 or WebM video. Use a <video autoplay muted loop playsinline> element instead.

Images loaded but never seen. Below-the-fold images without loading="lazy" are downloaded by every visitor, including those who never scroll. Hidden carousel slides and images inside closed tabs count too.

Our image delivery guide covers formats and compression in more detail.

JavaScript: the most expensive bytes

A kilobyte of JavaScript costs more than a kilobyte of image, because it has to be parsed, compiled and executed on the main thread. Start by finding out what’s in the bundles.

Look inside the bundle. Bundle analyzers such as rollup-plugin-visualizer, webpack-bundle-analyzer or source-map-explorer draw a map of which modules take up space. Typical findings:

  • a large utility or date library imported whole when a few functions are used;
  • locale or timezone data for every language, bundled by default;
  • two versions of the same library, pulled in by different dependencies;
  • polyfills for features every browser you support already has;
  • an admin or editor component bundled into public pages.

Check what actually runs. The Coverage panel in DevTools (open it from the command menu) shows, for each script and stylesheet, how many bytes were used during the session. Load the page, use it the way a visitor would, and look at what’s left unused. Large unused portions are candidates for code splitting: load that code only on the route or interaction that needs it.

// Load the heavy editor only when the user opens it
openButton.addEventListener('click', async () => {
  const { mountEditor } = await import('./editor.js');
  mountEditor(container);
});

Serve modern code. If your build still transpiles everything down to very old syntax and includes broad polyfill sets, check your browser targets. Modern syntax is usually smaller and runs faster.

The JavaScript cost guide explains how main-thread work shows up in the lab metrics.

Fonts: small files that add up

Fonts are rarely the largest category, but they are frequently wasteful:

  • Too many weights and styles. Regular, medium, semibold, bold and their italics can each be a separate file. Check which ones the design really uses.
  • Full character sets. A font that covers many scripts can be split into subsets with unicode-range, so a page in English doesn’t download Cyrillic or Vietnamese glyphs.
  • Old formats. WOFF2 is supported by every current browser. TTF, EOT and WOFF entries in the src list add nothing for modern visitors.
  • Icon fonts that ship hundreds of icons to display a handful. Inline SVG for the icons you use is usually lighter.

Filter the Network panel by “Font” to see the list. If you find more than four or five files on a typical page, there is probably room to cut.

The less obvious places

Once images, scripts and fonts are under control, look at these:

  • The HTML itself. Server-rendered frameworks often embed the page’s data as a JSON blob for hydration. Large lists, unused fields or whole API responses inlined into the HTML can make the document surprisingly heavy. Inlined base64 images and large inline SVG sprites add weight too, and unlike separate files they can’t be cached independently.
  • CSS. Frameworks and UI kits often ship far more CSS than one page needs. The Coverage panel shows unused rules; build tools can remove unused selectors, but test carefully, because class names generated at runtime can be removed by mistake.
  • Video. A background video on the home page can outweigh everything else combined. Use a poster image, preload="none" or preload="metadata", and consider whether mobile visitors need the video at all.
  • Third-party embeds. Maps, video players, social feeds and chat widgets each bring their own scripts, styles and images. A static preview that loads the real embed on click saves all of it for visitors who don’t interact.
  • Uncompressed text. HTML, CSS, JavaScript, SVG and JSON should be served with Brotli or gzip. Check the content-encoding response header for each.

Turn the audit into a budget

An audit fixes today’s page; a budget stops it from growing back. Pick limits from your current measurements after the cleanup, for example a maximum transferred size for JavaScript per template and a maximum size per hero image, and enforce them where changes happen:

  • In CI: tools such as size-limit fail a build when a bundle grows past its limit.
  • In the CMS: resize and convert uploaded images automatically, and cap the dimensions of the stored originals.
  • In review: require a short note on size impact for any new dependency or third-party tag.

A scanner such as PerfoMetrix reports a page’s total weight next to the image, compression and JavaScript findings from Lighthouse, which makes it easy to recheck a template after a release.

A short order of work

  1. Measure transferred and decoded size on your main templates, with cache disabled and after scrolling to the end.
  2. Fix image dimensions, formats and lazy loading first; this is usually the largest win.
  3. Analyze JavaScript bundles, remove duplicates and unused libraries, and split code that only some visitors need.
  4. Reduce font files to the weights, styles and subsets you use.
  5. Check the HTML payload, CSS, video and third-party embeds.
  6. Set budgets and enforce them in CI and in the CMS.

Repeat the measurement a few weeks later. Page weight rarely jumps; it creeps up one image and one dependency at a time, and a regular check is what catches it.

Tags:page weightimagesJavaScriptbundle sizeaudit