Server Components, One Year Later
A colleague asked me last week why our team cares so much about bundle size. I gave a rambling answer, so here is the longer, more organized version.
Most of the complexity was accidental. Over the years we'd layered three different approaches to bundle size on top of each other, and each one made sense at the time.
The problem
The first symptom was subtle. Nothing failed outright, but bundle size kept drifting in the wrong direction, and every fix seemed to make the next one harder.
The setup was ordinary: TypeScript at the edges, Vue doing most of the heavy lifting, and a handful of scripts nobody wanted to own. It worked well until usage roughly doubled over a single quarter.
The fix
The fix turned out to be smaller than expected. Instead of adding another layer, we removed one and leaned on what TypeScript already gives you out of the box.
Stripped of the project-specific bits, it looks like this:
export function debounce<T extends (...args: any[]) => void>(fn: T, wait = 200) {
let timer: ReturnType<typeof setTimeout> | undefined;
return (...args: Parameters<T>) => {
clearTimeout(timer);
timer = setTimeout(() => fn(...args), wait);
};
}
It's tempting to generalize this into a library. Resist that urge until you've needed it in at least three places.
A few things that helped along the way:
- Measure before and after, even if it's just a rough number in a spreadsheet.
- Pair on the first few changes so the knowledge doesn't live in one head.
- Write the migration guide before writing the migration.
What it cost us
We still haven't solved hydration. That's a post for another day, and honestly, I'm not sure we'll get it right the first time either.
The biggest cost was social, not technical. Changing how people think about bundle size takes much longer than changing the code.
What's next
That's it for now. Next time I'll dig into container queries, which turned out to be closely related.