← All writing

29 October 2026 · PerformanceWebpackCSS

One animation, 80 KB

How a bundle analysis on a hosted checkout found an animation library that drew a single loader, and what replacing it with CSS did for mobile load times.

A payment page has a few seconds to earn trust. Ours felt slow on mid-range phones: first contentful paint was 2.6 seconds and the largest contentful paint was 6.5 seconds. Before touching any code, I wanted to know what we were actually shipping.

Measure first

I ran Webpack Bundle Analyzer on the production build. The main bundle was 459 KB. Most of it was what you would expect from a React checkout: the framework, the app’s own code, form handling. One block stood out: an animation library took about 80 KB, roughly 17% of everything we shipped.

Step one: the lighter build

The library had a lighter build that drops features we didn’t use. Swapping to it brought the bundle down to 426 KB. That’s a decent win for a one-line change, and it would have been easy to stop there.

Step two: ask what it’s for

Then I looked at where the library was used. It rendered exactly one thing: a small loading animation. A whole animation runtime, for a spinner.

So I deleted the library and the JSON animation file and wrote the loader in CSS. The bundle went to 375 KB, 18.3% smaller than where we started. Alongside it I removed a sanitiser library we no longer needed, deferred the loader scripts and preconnected the font origin.

Try it

459 KB

Production bundle, before

First paint, mobile
2.6 s
Largest paint, mobile
6.5 s
Loading animation
still spins

The result

Before After
Bundle 459 KB 375 KB
First contentful paint (mobile) 2.6 s 1.9 s
Largest contentful paint (mobile) 6.5 s 5.5 s

The replacement looks like this. Two keyframes, no JavaScript, and it stops for anyone who has asked their device to reduce motion:

The replacement

0 KB of JavaScript · two keyframes · respects reduced motion

@keyframes spin { to { transform: rotate(1turn); } }
.ring {
  border: 3px solid var(--line);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin .8s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .ring { animation: none; }
}

What I’d tell myself before starting

  • The lighter build is a trap if it stops you asking the real question. The biggest win came from asking what the dependency was for, not from shrinking it.
  • Measure on the devices your customers use. The desktop numbers looked fine. The phone numbers told the truth.
  • Small numbers add up on a payment page. Every kilobyte is downloaded by every customer on every payment, often on mobile data.
↑↓ move↵ openesc close