DEV Community

Stop Shipping 700KB of JavaScript for a Button

Modern Web Development in 2026

A practical series about building faster, cleaner, more maintainable web applications without chasing every shiny thing.

There is a strange kind of web performance problem that does not look like a bug.

The page loads. The button appears. The design looks fine. The framework did its job. Lighthouse is not screaming loudly enough to block the release.

Then a real user taps the button on a mid-range phone while the main thread is busy doing work that should never have been shipped to the browser in the first place.

That is when the interface stops feeling like software and starts feeling like a negotiation.

This article is not an anti-JavaScript rant. JavaScript is one of the reasons the web is as capable as it is. The problem is simpler: we keep shipping client-side work that does not need to be client-side work.

The uncomfortable question

Before optimizing a component, ask this:

Does the browser need this code to make the first interaction useful?

If the answer is no, you have three options:

  1. move it to the server,
  2. delay it,
  3. delete it.

Most performance wins come from that boring list.

The common failure mode

A button starts as a button.

Then it becomes:

  • a component library import,
  • a theme provider dependency,
  • an analytics wrapper,
  • an icon package import,
  • a tooltip,
  • an animation primitive,
  • a client component boundary,
  • a hydration root,
  • and a tiny state machine nobody asked for.

The user sees one button. The browser receives a small town.

Budget the page before you polish it

A performance budget is not a punishment. It is a design constraint.

Use a simple budget before arguing about micro-optimizations:

Initial JavaScript:      under 170 KB compressed
Route-level JS:          under 80 KB compressed
Third-party scripts:     justified one by one
Critical CSS:            small enough to inline when useful
Images above the fold:   explicit dimensions and optimized format
Hydration islands:       only where interaction is required
Enter fullscreen mode Exit fullscreen mode

Your exact numbers can differ. The important part is that the budget exists before the page is already slow.

Start with the bundle, not the vibes

Run a bundle analyzer and look for the boring surprises:

npm run build
npx source-map-explorer "dist/**/*.js"
Enter fullscreen mode Exit fullscreen mode

Or use the analyzer that fits your stack:

# Next.js example
ANALYZE=true npm run build
Enter fullscreen mode Exit fullscreen mode

Then ask:

  • Why is this package in the initial route?
  • Is this library used for one function?
  • Did an icon import pull in more than one icon?
  • Is a date library doing work that Intl can do?
  • Is a chart library needed before the user scrolls to the chart?

Bundle analysis is humbling because it replaces opinions with weight.

Be suspicious of client boundaries

In server-first frameworks, the most expensive line in a file may be this one:

"use client";
Enter fullscreen mode Exit fullscreen mode

That line is not bad. It is a contract.

It says:

This component and the code it pulls in must be available to the browser.

So keep client components narrow:

// Good boundary: only the interactive part is client-side.
export function ProductPage({ product }) {
  return (
    <main>
      <ProductSummary product={product} />
      <AddToCartButton productId={product.id} />
      <ProductDetails product={product} />
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

The button can be interactive. The whole page does not need to hydrate because one button exists.

Delay what is not needed yet

Some code is useful, just not immediately.

Good candidates for lazy loading:

  • charts below the fold,
  • markdown editors,
  • maps,
  • video players,
  • complex filters,
  • admin-only panels,
  • onboarding tours,
  • non-critical animations.
const HeavyChart = dynamic(() => import("./HeavyChart"), {
  loading: () => <ChartSkeleton />,
  ssr: false
});
Enter fullscreen mode Exit fullscreen mode

Use this carefully. Lazy loading is not a magic spell. It moves cost. That is useful only when the moved cost no longer blocks the first useful interaction.

Third-party scripts are product decisions

Performance often dies by a thousand approved vendors.

Create a small script review:

### Third-party script review

- What user or business outcome does this script support?
- Does it run on every route?
- Can it load after interaction or consent?
- Who owns it?
- How do we remove it?
- What is the fallback if it fails?
Enter fullscreen mode Exit fullscreen mode

If nobody owns a third-party script, the browser owns the consequences.

Optimize for interaction, not screenshots

A page that appears quickly but cannot respond is not fast.

Look for long tasks:

new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    console.log("Long task:", entry.duration);
  }
}).observe({ type: "longtask", buffered: true });
Enter fullscreen mode Exit fullscreen mode

Then trace what blocks the main thread:

  • hydration,
  • large JSON parsing,
  • expensive rendering,
  • synchronous storage access,
  • client-side sorting/filtering of large lists,
  • heavy analytics startup.

The browser is not a server. Stop treating it like one.

The practical checklist

Before shipping a new route, check:

  • [ ] Is the initial JavaScript budget visible in CI?
  • [ ] Are client components as small as possible?
  • [ ] Is non-critical UI lazy loaded?
  • [ ] Are third-party scripts justified?
  • [ ] Are images sized and optimized?
  • [ ] Is the main thread free before the first important interaction?
  • [ ] Did you test on a slower device or throttled profile?
  • [ ] Can the page still be useful if enhancements arrive late?

The boring truth

Most users do not care which framework you used.

They care that the page loads, responds, and gets out of their way.

The fastest JavaScript is the JavaScript you never send. The second fastest is the JavaScript you send later. The third fastest is the JavaScript you actually needed.

Sources


Thanks for reading.

You can find me here:

Top comments (0)