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.
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:
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.