What a prerendered React site actually costs in bytes
- React
- Performance
- SEO
This site is React 19 and React Router 7, built with Vite, and every route is prerendered to static HTML at build time. Here are the actual numbers rather than the usual hand-waving.
The bill
vendor.js 228.9 kB raw 73.2 kB gzipped
index.js 193.1 kB raw 57.9 kB gzipped
index.css 64.9 kB raw 10.9 kB gzipped
entry JavaScript, gzipped: 131.1 kB
of which React + Router: ~116 kBReact and React Router account for roughly 116 kB of that before a single line of application code is written. Our own code, across 31 prerendered pages with all content statically imported, adds about 15 kB gzipped.
That ratio is the thing worth internalising. Shaving your own code is nearly pointless. The framework is the budget.
What prerendering buys
First paint does not wait on JavaScript, because the HTML already contains the page. Measured on the throttled mobile profile Lighthouse uses:
- Total Blocking Time: 0 to 10 ms
- Cumulative Layout Shift: 0
- Accessibility, Best Practices and SEO: 100
- Largest Contentful Paint: 2.2 s, rising to about 3.2 s once an analytics tag is added
The crawler gets real markup with a real title, real headings and real JSON-LD, with no dependency on JavaScript executing. For a site whose primary job is being found, that is the entire argument.
The constraint nobody mentions
If every route is prerendered, you cannot lazy load routes. A Suspense fallback during hydration would replace markup the user is already reading and cost you a layout shift, which is the one metric prerendering gives you for free.
So the usual advice to code split by route is actively wrong here. We split vendor from index, for cache longevity across deploys, and that is the only split. Every visitor downloads every page of content.
Which means your content grows your bundle. Ten pages of real copy cost us about 12 kB gzipped. That is the trade: a bigger single download in exchange for instant navigation and zero layout shift, on a site small enough for it to be the right call.
When this stops being the right answer
At some page count the all-content-everywhere model breaks down, and the honest signal is Total Blocking Time rather than bundle size. Ours is still effectively zero, so the bytes are not costing anybody anything measurable. The day TBT moves is the day to revisit it, and not before.
Spend effort on bytes only when a measurement tells you to. A budget you enforce by habit rather than by evidence just makes the site worse in other ways.
One last thing. Measure against a server that behaves like production. We serve dist with the same try_files resolution and compression rules nginx uses, because the dev preview server does neither and will tell you comfortable lies about roughly 25 Lighthouse points.
More from us
Want this standard applied to your systems?
We write these up because they are the same decisions we make on client work. Tell us what you are building and we will tell you what we would do.
A reply from a developer. Not a sales pipeline. The person who reads your message is the person who would do the work.
An honest answer on fit. If an off-the-shelf product solves your problem better than a custom build, we will tell you which one.
No obligation. A first conversation costs nothing and commits you to nothing. Discovery output is yours either way.