⚡FTJ
← Blog
DeveloperBy The FreeToolJet Team

I Weighed My Own Site in Production: 1.8 MB of JavaScript on Every Tool Page

I measured every page I ship: HTML is tiny once gzipped, but tool pages load 1.8 MB of JavaScript — including a PDF engine on the JSON formatter. Here are the real numbers and the fix.

Everyone who writes about performance has a confession coming. I've spent the past week telling people to shrink their images and audit their JavaScript — so this afternoon I pointed the same tooling at my own site. The content pages were fine. The tool pages were not.

Short version: my blog and legal pages ship about 190 KB of gzipped JavaScript. My tool pages ship about 571 KB — roughly three times as much. And the single largest reason is that the JSON formatter downloads a PDF engine.

What I measured

I ran a production build (next build then next start), fetched a handful of representative pages with curl, parsed the _next/static/*.js references out of each page's HTML, and summed the gzipped size of every chunk that page actually loads. Here are the real numbers:

PageHTML rawHTML gzipJS filesJS rawJS gzip
/ (home)151.3 KB20.2 KB9638.6 KB190.0 KB
/blog/…-jpg-mistake89.2 KB15.4 KB9638.6 KB190.0 KB
/privacy-policy78.2 KB12.7 KB9638.6 KB190.0 KB
/convert/kg-to-lbs78.3 KB11.2 KB10656.0 KB193.8 KB
/zh81.3 KB11.2 KB9638.6 KB190.0 KB
/tools/json-formatter77.0 KB12.0 KB121825.7 KB570.9 KB
/tools/image-compressor80.7 KB12.6 KB121825.7 KB570.9 KB

Two things jump out. First, the HTML is *not* the problem: the homepage is 151 KB of markup raw but 20 KB gzipped — an 87% squeeze. Every page compresses down to 11–20 KB of HTML. Second, the tool pages all load the *exact same* 1,825.7 KB of JavaScript, to the byte, whether you're on the JSON formatter or the image compressor. Identical byte counts across unrelated tools is a smell.

The 726 KB chunk that has nothing to do with the page

Sorting the chunks by size, the top three are:

  • 725.9 KB — contains pdf-lib, PDFDocument, fontkit, startxref, pako
  • 461.0 KB — also pdf-lib
  • 221.0 KB — react-dom

That first chunk is a PDF-generation engine with an embedded font toolkit. It loads on the tool pages and *not* on the content pages. Which means: open the JSON Formatter and your browser downloads a PDF library. Open a password generator, same thing.

Why it happens

My tool routes share one barrel file: src/app/tools/slug]/tool-components.tsx maps every tool slug to a component with a static import. A static import means "bundle this no matter what," so the tool route's client bundle ends up containing *all* the tools — image tools, text tools, and the [PDF Merge & Split tool with its pdf-lib dependency. One page pulls them all in.

This is the classic barrel-import tax, and Server Components hide it beautifully: my HTML got tiny because rendering moved to the server, but one imperative import in a shared file quietly undid the win on the client.

What I tried (and why it's still not fixed)

The obvious fix is to load each tool's code only when that tool is opened. I tried three routes, and none of them cleanly worked under the bundler this site builds with:

  • next/dynamic inside the server component: the bundler folded it back into a static import, so nothing split (still 16 chunks).
  • A client-side ToolLoader with a dynamic map: this finally split the tools apart (16 → 84 chunks), but a shared ~465 KB pdf-lib chunk still got preloaded on every tool page — including the JSON formatter.
  • Lazy import("pdf-lib") inside the two PDF tools: the bundler hoisted it right back into the eager tool chunk.

I also owe you a precision correction to my own framing above: pdf-lib was preloaded, not executed — the JSON formatter downloaded it but never ran a line of PDF code. That is still pure waste (bandwidth), just not a parse-time cost, and I would rather state it accurately than let the bigger number stand on its own.

So the static map of every tool inside a single [slug] route keeps pdf-lib in the eager chunk no matter how I lazy-import it. A real fix means either switching the build's bundler or giving each tool its own route — and I want to test that properly before shipping. I am deliberately leaving this as a documented, honest TODO instead of publishing numbers I have not verified. When I do land it, the before/after figures will go in this exact table.

How to reproduce

next build && next start, then for each URL run curl -s "$URL", grep the /_next/static/….js sources out of the HTML, and sum the gzipped size of those files with gzip -9. Measuring the chunks a page *actually references* is more honest than trusting one "total bundle size" number, because it's exactly what the browser will fetch for that URL.

The uncomfortable lesson: it's far easier to see another site's bloat than your own. If you run a tool or component directory, go count the bytes on two pages that shouldn't share code — identical numbers will tell you exactly where the leak is.

FT
The FreeToolJet Team
Maintainer of FreeToolJet

FreeToolJet is a small, independent project. The tools and guides here are written and maintained by the site's creator, who got tired of ad-heavy tool sites that upload your files to a server and built a private, browser-only alternative instead. If a guide is wrong or a tool breaks, tell us through the contact page — we read every message.

More about us →

Try These Tools

More Articles