All articles
E-commerceJune 4, 2026 6 min read

Stop Lazy-Loading Your Hero Image: A Checkout-Page Performance Audit We Run Every Quarter

Checkout is where revenue dies one millisecond at a time. Here's the quarterly audit we run on Shopify and custom stacks — what we measure, what we ignore, and the seven things we always find broken.

Stop Lazy-Loading Your Hero Image: A Checkout-Page Performance Audit We Run Every Quarter

Checkout is the most expensive page in your store to break and the one nobody audits. Teams pour weeks into PDP redesigns and homepage hero tests, then ship a third-party fraud script that adds 600ms to the only page where every visitor is already a buyer.

We run a checkout performance audit on every store we manage, once a quarter. It takes about half a day and almost always recovers measurable revenue. Here's the playbook.

Why checkout deserves its own audit cycle

Product pages get the glory. They're indexed, they're shared, they're where merchandisers spend their time. But the math on checkout is brutally simple: a visitor on /checkout has already converted on intent. Every millisecond of friction between there and the thank-you page is pure margin loss.

The other reason checkout drifts: it's where third-party scripts accumulate. Fraud tools, address validators, BNPL widgets, tax engines, analytics, A/B testing snippets, chat. Each one was added by someone with a good reason. Nobody removes them. After eighteen months, the page that should be the leanest in your store is carrying more JavaScript than the homepage.

What we measure (and what we ignore)

Lighthouse scores are not the goal. We've seen stores with a Lighthouse 92 on checkout that bleed conversions on mid-range Android. We care about real-user metrics on the devices and networks your actual customers use.

The core list:

  • LCP on checkout step 1, segmented by device class and country
  • INP on the primary form fields (email, address, payment)
  • Time to first payment-method render — a custom mark we set
  • Total blocking time during address autocomplete
  • Checkout completion rate by step, not just overall
  • Error rate on payment submit, segmented by gateway

We ignore Speed Index on checkout. It rewards visual completeness, and checkout is a form — visual completeness is not the same as interactive readiness.

Setting up the custom marks

If you're on a custom stack or Shopify Plus with checkout extensibility, drop these in:

// Fire when the payment method selector is interactive
performance.mark('payment-methods-ready');

// Measure from navigation start
const measure = performance.measure(
  'time-to-payment-ready',
  'navigationStart',
  'payment-methods-ready'
);

// Ship to your RUM endpoint
navigator.sendBeacon('/rum', JSON.stringify({
  metric: 'time-to-payment-ready',
  value: measure.duration,
  device: navigator.userAgent,
  connection: navigator.connection?.effectiveType
}));

This single mark has caught more regressions for us than any synthetic test. A new fraud script that delays payment render by 400ms shows up here within a day of release.

The seven things we always find

After running this audit across dozens of stores, the same issues surface. Not always all seven, but usually four or five.

1. Fraud and risk scripts loaded synchronously

Fraud vendors love telling you their script must load before checkout renders. It rarely needs to. Most modern fraud tools (Signifyd, Riskified, Forter, Kount) collect signals passively and only need to complete before the order is submitted to the gateway. That's a window of 30 seconds or more, not the first paint.

Move them to async or defer them until the user starts typing in the email field. We've consistently seen 200–500ms improvements in LCP from this single change.

2. Address autocomplete that blocks the main thread

Google Places, Loqate, SmartyStreets — these are all good products, badly implemented. The common pattern: a debounced keystroke handler that does synchronous JSON parsing of large responses on the main thread, plus DOM thrashing on every dropdown render.

If you control the implementation, move the parsing into a Web Worker. If you're using a vendor widget, check whether they offer a virtualised dropdown — most do now, and it's usually a config flag away.

3. Tax calculation on every keystroke

We've audited stores that call their tax engine (Avalara, TaxJar) on every field blur in the address form. Some call it on every keystroke. Each call is a network round-trip and often a re-render of the order summary.

Debounce tax calculation to fire once when the address is structurally complete (postcode + country at minimum), and cache aggressively. Most jurisdictions don't change tax rates within a session.

4. Order summary re-renders on every input

This is mostly a framework problem. If your checkout is built in React, Vue, or Hydrogen, check whether the order summary component is subscribing to form state changes it doesn't need. We've seen summaries re-render fifty times during a single address entry because they were watching the entire form object.

Memoise. Subscribe to specific fields. Use selectors. This is basic, but it's missing more often than you'd guess.

5. Webfonts blocking payment method icons

Visa, Mastercard, PayPal, Apple Pay — these icons are often served as icon-font glyphs or inline SVGs that depend on a webfont being loaded. Until the font lands, the payment method row either shows nothing or shifts when the icons appear, hurting CLS and creating that horrible moment where the user can't tell which option they're clicking.

Serve payment icons as static SVGs or PNGs from your CDN. Self-host. Don't make Stripe's recommended payment row depend on Google Fonts.

6. Polyfills shipped to modern browsers

If your build still ships a single bundle with polyfills for IE11 or old Safari, you're sending 30–80KB of dead code to every modern customer. The module/nomodule pattern is over six years old. Use it.

For Shopify themes, this is usually a Liquid-level change in theme.liquid to conditionally load the legacy bundle. For headless stacks, your build tool (Vite, Next.js, Astro) handles it natively if you configure the browserslist correctly.

7. Analytics firing before the page is interactive

The single biggest INP killer we see: GTM containers with thirty tags, all firing on DOMContentLoaded, all competing with the user's first interaction. The user taps the email field, and nothing happens for 300ms because the main thread is parsing analytics payloads.

Move non-essential analytics to fire on requestIdleCallback or after the first user interaction. Server-side tagging (via Shopify's Customer Events or a custom GTM server container) is worth the setup cost on high-volume stores.

How we structure the audit

We block out four hours. Two engineers, one product person. Screen-share, one driver, two observers taking notes.

The order:

  1. Pull 30 days of RUM data. Segment by device and country. Identify the worst-performing cohort.
  2. Reproduce on a throttled device. We use a mid-range Android (often a real Pixel 4a or equivalent) on throttled 4G. Not Chrome DevTools throttling — that lies about CPU.
  3. Record a performance trace through the entire checkout flow, from cart to thank-you.
  4. Map every third-party request to a business owner. If nobody can name why a script is there, it's a candidate for removal.
  5. Write up the findings as a prioritised list, with estimated effort and expected impact.

The write-up matters. Performance work without an owner and a number gets deprioritised the moment a merchandiser wants a new banner.

When to escalate beyond an audit

If you've run this audit twice and the same issues keep coming back, you have a process problem, not a performance problem. Common causes:

  • No performance budget enforced in CI
  • Third-party scripts added without review
  • A theme or framework that makes the right thing hard

At that point, the conversation shifts from "fix the checkout" to "who owns checkout performance, and what's their veto power over new scripts?" If you're on Shopify Plus, checkout extensibility gives you a much cleaner extension model than the old checkout.liquid did, and it's worth the migration if you haven't done it yet.

Where we'd start

If you've never audited your checkout, do this Monday: open your RUM tool, filter to /checkout*, segment by device, and look at the p75 LCP for mobile. If it's over 2.5 seconds, you have at least 200ms of easy wins waiting in your third-party script list. Pull that list, ask each vendor whether their script can be deferred, and ship one change. Measure for a week. Then do the next one.

The stores that win at checkout performance aren't the ones that did one big rewrite. They're the ones that ran this audit every quarter for three years and never let the script count creep back up.

#E-commerce#Performance#CRO#Shopify#Checkout

Want a team like ours?

72Technologies builds production software for the kind of teams who actually read this blog.

Start a project