I'm going to tell you something that most web performance articles won't. The speed problem with your website is not your images, not your fonts, not your lazy loading strategy. It's physics. Specifically, the physics of how TCP decides to send your first page to a browser.

And if you picked React, Angular, or Next.js for a content site, you already lost this race before writing a single line of application code. Not because these are bad tools. They're excellent tools. But do you really need a delivery truck - or an 18-wheeler - to drop a letter in a mailbox?(Paraphrase of a point made by a wise man who spent too many years watching Angular bundles load)

The 14KB wall

When a browser connects to your server for the first time, something called TCP slow start kicks in. The server doesn't know how much bandwidth the connection can handle, so it starts cautious. It sends exactly 10 TCP packets without waiting for acknowledgment. Each packet carries a maximum of 1,460 bytes of payload (that's 1,500 bytes of Ethernet MTU minus 40 bytes of TCP/IP headers).

The math: 10 packets x 1,460 bytes = 14,600 bytes. Roughly 14KB.

This isn't a guideline. It's RFC 6928, published in 2013, implemented in every modern TCP stack on the planet. After those initial 10 packets, the server stops and waits for the browser to acknowledge receipt. Only then does it double the window and send more.

If your entire page fits in 14KB (compressed), the browser gets everything in that first burst. One round trip. If it doesn't fit, you pay an extra round trip per doubling: 14KB, then 28KB, then 56KB. On a 100ms connection, that's an extra 100ms per step. On satellite or a flaky mobile connection, it can be 612ms or more per extra trip.

The thing is - 14KB compressed is actually generous. With Brotli compression at ~70% ratio, that's roughly 47KB of uncompressed HTML, CSS, and JavaScript. Enough for a complete, useful page. If you build it right.

The framework weigh-in

I ran the numbers. Here's what popular frameworks ship to the browser for a minimal hello-world application, gzipped:

Vanilla HTML0 KBAstro (default)0 KBSvelte 52 KBSolidJS4 KBVue 318 KBAngular 1948 KBReact 19 + DOM55 KBNext.js (App Router)65 KBFramework JavaScript runtime shipped to browser (gzipped)bundlephobia.com, framework docs, byteiota.com
Chart data
LabelValue
Vanilla HTML0 KB
Astro (default)0 KB
Svelte 52 KB
SolidJS4 KB
Vue 318 KB
Angular 1948 KB
React 19 + DOM55 KB
Next.js (App Router)65 KB

The chart measures only JavaScript runtime - the framework code your browser must download and execute before it can do anything useful. Vanilla HTML and Astro ship zero JS by default (your page still has HTML and CSS, but no framework overhead).

Read that chart again. React 19 with ReactDOM ships 55KB gzipped just for the framework runtime. That's nearly four times the 14KB budget, and you haven't rendered a single component yet. Angular is worse at 48-130KB depending on features. Next.js, despite server components, still drops 50-80KB of JavaScript on the client for a minimal page.

Vanilla HTML, Astro, and Svelte are the only frameworks that can physically fit inside the first TCP window. Everything else requires at least one additional round trip before the browser has enough JavaScript to do anything.

This is not an optimization problem. You can't tree-shake, code-split, or lazy-load your way past a 55KB runtime. It's the floor, not the ceiling.

"But HTTP/2 changes everything!"

I hear this objection constantly, and it's partially right. Barry Pollard from the Google Chrome team wrote a detailed Wireshark analysis showing that during the TLS handshake, the client sends ACKs that grow the congestion window before HTML transmission even begins. On Amazon.com, he observed at least 20 packets acknowledged before the first byte of HTML, meaning the effective first-burst window was closer to 28-57KB.

So the 14KB number is more of a conservative estimate than a hard wall. Fair enough.

But here's what the HTTP/2 optimists miss: HTTP/3 (QUIC) still has the same limit. RFC 9002 specifies an initial congestion window of 14,720 bytes. The protocol changed. The physics didn't. And for users on high-latency connections - satellite, 2G/3G, the developing world - the penalty for exceeding that first window is still measured in hundreds of milliseconds.

Pollard himself concludes: "Don't sweat too much over this magic 14KB number, but the general advice still stands." The general advice being: keep pages small and put important resources first. Which is what I've been saying.

The money on the table

If you think this is academic, here are numbers that CFOs understand:

Amazon found that every 100ms of added page load time cost them 1% in sales. At 2025 revenue, that's roughly $3.8 billion per 100ms. Pinterest rebuilt their pages for performance and saw a 40% decrease in wait time, 15% increase in signups. Vodafone improved LCP by 31% and got 15% more leads, 11% more cart visits, 8% more sales. Rakuten improved Core Web Vitals and saw a 53% increase in revenue per visitor.

For SEO, the data is equally clear. A study of 143,827 URLs found that sites in positions 1-3 had a median TTFB of 180ms versus 420ms for positions 7-10. Pages with LCP under 1 second rank 7.5 positions higher on average than those over 4 seconds.

You know what gets you a fast TTFB and low LCP? Sending less than 14KB on the first round trip.

developers.pl in numbers

Here's what this site actually sends:

  • HTML (SSR): 7.2 KB - fits inside 14KB with room for HTTP headers
  • Total resources: 620 KB (fonts, CSS, scripts for embeds)
  • Transferred (compressed): 273 KB

The 7.2 KB HTML arrives in the first TCP burst. The browser can start rendering immediately. Fonts and CSS load in subsequent round trips, but the content is already readable. No spinner. No skeleton screen. No hydration dance.

This isn't because I'm a performance wizard. It's because Astro ships zero JavaScript by default. The framework runtime is 0 KB. The page weight is just my HTML and whatever CSS is needed for that specific page. Everything else loads on demand, if needed at all.

Compare that to a hypothetical React implementation of the same page. Even with server-side rendering, the browser would need to download, parse, and execute 55KB+ of framework JavaScript before it could make the page interactive. On a fast connection, you might not notice. On a train going through a tunnel, or a phone on 3G in rural Poland, or a satellite link - you notice.

What to do about it

If you're building a content site, a blog, a documentation portal, a marketing page, a newspaper - you don't need a 55KB JavaScript runtime. Full stop. Use Astro, use Hugo, use plain HTML. Ship zero JS by default, add islands of interactivity where you actually need them.

If you're building an application - a dashboard, a real-time collaboration tool, an IDE - React and Angular earn their weight. The runtime cost is amortized across long sessions where the user never reloads. The 14KB rule matters less when the user stays for an hour.

The problem is when people use application frameworks for content delivery. When the marketing site runs on Next.js because "the team knows React." When the documentation portal is an Angular SPA because "it integrates with our design system." When the blog is a Create React App because someone started it in 2019 and nobody wanted to rewrite.

The WordPress elephant

And then there's WordPress. The elephant in the room that runs 43% of the web.

I run a hosting company - chmurka.pl. I see what people deploy every day. The pattern is always the same: a business card site - five pages, a contact form, maybe a Google Maps embed - running on WordPress with Elementor, WooCommerce (because "maybe we'll add a shop later"), three SEO plugins fighting each other, a slider nobody asked for, and a "security suite" that weighs more than the content it protects.

The result? A 4MB homepage that takes 8 seconds to load, served on PHP 7.4 (which is generous, honestly). And then the client calls asking why their site is slow, why Google doesn't rank them, why mobile users bounce. The answer is always the same: your five-page business card is running on infrastructure designed for an e-commerce platform.

That business card could be a static HTML file under 14KB, hosted for free on Cloudflare Pages, loading in 200ms. Or hosted on any decent provider, because static files need nothing - no PHP, no MySQL, no wp-cron eating CPU cycles on every visit, no junk accumulating in the database.

Instead it's a WordPress instance that needs monthly updates, plugin compatibility checks, and a prayer that the next Elementor update doesn't break the layout. I've seen clients spend more on WordPress maintenance than on the hosting itself. They pay someone to update plugins, fix conflicts, and restore backups when an update goes wrong. For a business card. Five pages. A phone number and a Google Maps embed.

The WordPress tax on a simple business card site is not technical debt. It is a self-inflicted wound. "Ease of use" is the marketing label that hides the bill.

Every one of those choices means your content can never arrive in the first TCP burst. Not because your code is bad. Because the framework's floor is above the ceiling.

14,600 bytes. Either you fit, or you wait for the next round. Physics does not negotiate.

Sources: RFC 6928, endtimes.dev, Tune The Web analysis, RFC 9002 (QUIC), Neil Patel speed study, WPO Stats, Bundlephobia