Performance work has a reputation for being about clever tricks — code splitting, tree shaking, some obscure webpack flag. In practice, most of the meaningful gains I've seen on real projects came from a much shorter, more boring list. Here's what actually moved the needle, roughly in order of impact.

1. Fonts were the biggest layout shift culprit, every time

On more than one project, the single largest Cumulative Layout Shift contributor was web font loading — text rendering in a fallback font, then reflowing when the real font arrived. The fix is almost always the same: preconnect to the font host, use font-display: swap, and specify a fallback font stack with similar metrics so the reflow is minimal even when it happens.

index.htmlhtml
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="...&display=swap" rel="stylesheet">

2. The biggest image on the page decides your LCP

Largest Contentful Paint is, in practice, usually a single hero image or heading. Once I started treating that one element as a first-class performance concern — proper width/height to prevent layout shift, a modern format, correct sizing for the viewport, and no lazy-loading on it specifically since it needs to load immediately — LCP scores improved more than any other single change I made.

Don't optimize every image equally. Find the one that decides your LCP score and give it disproportionate attention.

3. Most client-side JavaScript wasn't earning its cost

Auditing a bundle with real numbers attached is humbling. On one project, a date-formatting library alone accounted for more shipped JavaScript than the rest of the page's interactivity combined — for a feature that could have been three lines of Intl.DateTimeFormat. The habit that stuck: before adding a dependency, check what it costs in kilobytes, not just what convenience it offers.

ChangeTypical impact
Removing a heavy date/utility library for native APIsLarge bundle-size reduction
Converting a static component from client to serverRemoves its JS from the client bundle entirely
Correct image sizing + modern formatsOften the single biggest LCP improvement
Font preconnect + swap + fallback matchingNear-elimination of font-related layout shift

4. Server Components changed the default, not just the ceiling

Moving to the Next.js App Router didn't just make it possible to ship less JavaScript — it made shipping less JavaScript the default, which matters more than the ceiling of what's possible. When "server-rendered" is the starting point and "client-side" requires an explicit opt-in, the aggregate amount of JS shipped across a whole application drops meaningfully, just from the direction of least resistance changing.

5. Measuring in the wrong place gives you the wrong answer

Lighthouse on a fast office wifi connection told a very different story than real user data from actual visitors on mid-range phones on mobile networks. Once a project has real traffic, field data (Core Web Vitals from actual users) matters more than lab scores. I still use Lighthouse constantly during development — it's a good regression check — but I stopped treating a 100 in the lab as the goal in itself.

What I check before calling a page "fast"

  • Is the largest visible element sized and prioritized correctly?
  • Does any component ship JavaScript it doesn't strictly need?
  • Are fonts preconnected and set to swap with a matched fallback?
  • Does the page feel fast on a throttled mobile connection, not just in the lab?

None of this is exotic. That's kind of the point — performance work that actually holds up in production is mostly about consistently doing the boring things, not finding a clever trick that fixes everything at once.