Performance

Website Performance Testing: A Practical Checklist for Developers

Measure the right things, in the right order: Core Web Vitals, payload weight, render-blocking assets, third-party scripts and the fixes that move the numbers.

12 min read

Performance work goes wrong in a predictable way: someone runs Lighthouse, sees an orange score, and starts optimising whatever the report listed first. Two days later the score has moved four points and nobody can tell why.

The fix is to measure in a fixed order, from the outside in, and to only change one thing at a time. This checklist follows that order.

Before you measure anything

Three setup decisions determine whether your numbers mean anything.

  1. 1

    Test the page users actually land on

    Not the homepage, unless that's where traffic goes. Check analytics for your top entry pages and test those. A product page with a gallery and reviews behaves nothing like a marketing homepage.

  2. 2

    Decide between lab and field data

    Lab data (Lighthouse, WebPageTest) is reproducible and lets you compare before and after. Field data (the Chrome UX Report, or your own RUM) is what Google uses for ranking and what your users actually experience. You need both, and they will disagree.

  3. 3

    Throttle honestly

    Testing on a wired connection with a fast laptop tells you nothing about a mid-range Android on 4G. Use DevTools' Network throttling at Slow 4G and CPU throttling at 4× as a baseline, and don't be alarmed the first time.

1. Core Web Vitals: the three numbers that matter

MetricGoodWhat it measures
LCP (Largest Contentful Paint)≤ 2.5sWhen the main content finished rendering
INP (Interaction to Next Paint)≤ 200msHow quickly the page responds to input
CLS (Cumulative Layout Shift)≤ 0.1How much things move around while loading

INP replaced First Input Delay in 2024, and it's a much harsher measure: it looks at every interaction across the visit, not just the first one. Sites that scored well on FID often score poorly on INP.

Diagnosing LCP

First find out what the LCP element is, because people are wrong about this constantly. Chrome marks it in the Performance panel timeline. It's usually a hero image or a large heading.

Then break the time down. LCP has four parts: time to first byte, resource load delay, resource load time, and render delay. Which part dominates tells you what to fix:

  • Slow TTFB — server, database or CDN problem. No amount of front-end work fixes it.
  • Long load delay — the browser found out about the image late. It's being discovered by JavaScript, or it's behind a lazy-load that shouldn't apply. Preload it.
  • Long load time — the image is too big. See the image analysis guide.
  • Long render delay — render-blocking CSS or JS is holding the paint back.

Diagnosing CLS

Layout shift comes from a short list of causes, and they're all preventable:

  1. 1Images and videos without width and height attributes (or an aspect-ratio)
  2. 2Ads, embeds and iframes injected into unreserved space
  3. 3Web fonts swapping in with different metrics
  4. 4Content inserted above existing content, like a cookie banner that pushes the page down
  5. 5Animating top/left/height instead of transform

Chrome's Performance panel flags shifts in the Experience track, and clicking one shows the element that moved. For a whole-page sweep instead, Missing Width/Height Attributes in WDT finds every image that hasn't reserved its space. It's the cheapest CLS fix there is.

Diagnosing INP

INP is a main-thread problem. Long tasks block input handling, and long tasks come from large JavaScript bundles, expensive event handlers, and layout thrashing in loops. Record an interaction in the Performance panel and look for tasks over 50ms in the main thread flame chart.

2. Payload: how much are you actually sending?

Before optimising anything clever, count the bytes. Open the Network panel, disable cache, hard-reload, and read the summary bar at the bottom: number of requests, transferred size, resource size, load time.

Rough targets for a content page, uncompressed sizes:

  • HTML — under 100KB
  • CSS — under 100KB total, and one or two files, not fifteen
  • JavaScript — under 300KB is a reasonable ceiling; under 150KB is good
  • Images — under 1MB total for the initial viewport
  • Fonts — two families maximum, woff2 only, subset if you can
  • Total requests — under 50 for the initial load

These aren't laws. They're the point at which you should have a reason. Web Developer Tools reports the same totals in one click through Resource Count Summary and Resource Type Breakdown, and Find Large Resources surfaces anything over 1MB immediately.

3. Render-blocking resources

Anything in <head> that blocks parsing delays your first paint. Synchronous scripts block; stylesheets block rendering; @import inside CSS blocks and serialises the fetch.

html
<!-- Blocks parsing until fetched and executed -->
<script src="/analytics.js"></script>

<!-- Downloads in parallel, runs after parse -->
<script src="/analytics.js" defer></script>

<!-- Downloads in parallel, runs whenever ready — order not guaranteed -->
<script src="/analytics.js" async></script>

defer is the right default for almost everything. async is for independent scripts that don't touch the DOM. Neither is right for a script that must run before first paint, of which there are far fewer than most codebases assume.

Two tools name the offenders directly: Detect Render Blocking CSS and Detect Render Blocking JS. Quicker than reading a Lighthouse opportunity list and mapping each entry back to a tag by hand.

4. Third-party scripts

This is usually where the real weight is, and it's usually the part nobody owns. Analytics, tag managers, chat widgets, A/B testing, heatmaps, consent managers, ad tech. Each one was added for a good reason by someone who has since left.

Audit them properly:

  1. 1

    List every third-party domain the page contacts

    WDT's Detect Third-Party Scripts, Third-party Request Report and Domain Breakdown produce this list. Tag managers make it longer than you expect, because they load things that load things.

  2. 2

    Measure the cost of each

    In DevTools, block a domain (right-click a request → Block request domain) and reload. The difference in LCP and total blocking time is that vendor's real cost.

  3. 3

    Ask whether anyone still uses it

    The heatmap tool nobody has logged into for a year is pure cost. This is a conversation, not a code change, and it's often the highest-leverage one available.

  4. 4

    Defer what survives

    Almost nothing third-party needs to run before first paint. Load it after the page is interactive, or on interaction.

5. Caching and delivery

Repeat visits should be dramatically faster than first visits. If they're not, your cache headers are wrong. Check that hashed assets get a long max-age with immutable, that HTML has a sensible short policy, and that text responses come back with Content-Encoding: br or gzip.

The HTTP headers guide covers exactly what to look for. WDT's Detect Cache Headers, Detect Compression and Cache Hit / Miss Summary answer these without opening the Network panel.

6. DOM size and rendering cost

A very large DOM makes every style recalculation and layout more expensive, and it's a common cause of poor INP on pages that look simple. Lighthouse warns above roughly 1,400 nodes; the practical pain starts higher, but depth matters as much as count.

Run the DOM Size Analyzer and you get total nodes, maximum depth and maximum children in one place. Deeply nested wrappers from a component library are the usual cause, and the fix is structural rather than a tweak.

Web Developer Tools popup with panels highlighting debugging, network inspection, responsive testing, performance analysis and auditing capabilities
Performance, Network and Resource Explorer categories cover the measurement side; the fixes still happen in your codebase.

7. Fonts

  • woff2 only — every browser you care about supports it
  • font-display: swap so text is visible immediately, or optional if you'd rather avoid the swap entirely
  • Preload the one or two faces used above the fold, and no more
  • Subset to the characters you need if you're shipping a large family
  • Match fallback metrics with size-adjust and ascent-override to reduce the shift when the real font arrives

The Font Usage Report lists every face the page actually loaded. It's common to find four weights loaded and two used.

The order to work in

If you do nothing else, do these five in this sequence. They're ordered by effort-to-impact ratio, which is not the order any automated report will give you.

  1. 1Fix the LCP image: correct format, correct size, preloaded, not lazy-loaded
  2. 2Add width/height to every image to kill the easy CLS
  3. 3Move every non-essential script to defer and audit the third parties
  4. 4Turn on compression and fix cache headers
  5. 5Then, and only then, start splitting bundles

Bundle splitting is the work people want to do first because it feels like real engineering. It's usually fourth or fifth in terms of what actually moves the numbers.

Images are almost always the biggest single win, so auditing them properly is the natural next step. Before a release, fold the performance pass into the wider pre-launch QA checklist.

Related reading

All 15 articles
Performance

How to Analyze Images on a Web Page

Audit every image on a page: what it weighs, whether it's oversized for its slot, which format it should be in, and whether the alt text is doing its job.

10 min read
Network

How to View HTTP Headers in Chrome

See request and response headers for any page or asset in Chrome, plus a reference for the headers that actually change how your site behaves.

9 min read