About Stackra
Stackra is a website audit for small and mid-sized businesses, with a free plan plus paid Starter and Pro tiers. Our own site is a React single-page app served by Express, around 42 marketing and blog routes in total. We hit this problem on our own site, which is why we have a story to tell about it.
Why we did not switch to Next.js or Remix
Server-side rendering frameworks like Next.js, Remix, and Nuxt solve the same problem by rendering HTML on a Node server for every request. They are the industry default answer for AI-visible websites. We considered them and decided against, for four reasons.
- •Our marketing pages are essentially static. The same HTML works for every visitor. SSR's main value, dynamic per-request HTML, did not apply to anything we needed crawlers to see.
- •Migrating to Next.js or Remix is a real architectural change: routing rewrite, data-fetching refactor, layout migration. Adding a build-time prerender step on top of our existing setup is additive, not architectural.
- •Static HTML on disk is the cheapest, fastest, most cacheable response a server can produce. SSR adds a render server we have to run, monitor, and pay for.
- •The pages on Stackra that genuinely need dynamism, like the report viewer and dashboard, are intentionally not indexable. They never needed SSR for SEO.
What our corpus says about shipping HTML
We made this call on reasoning in April, then measured it afterwards. Across 7,203 US hand-built sites, custom code with no CMS, the same shape as stackra.app, we classified each site's raw HTML before any JavaScript ran and compared that against real-world Core Web Vitals.
Source: Stackra corpus, 7,203 US hand-built sites with Chrome UX Report field data, classified from raw HTML captured in the 2026 Q2 crawl. 86.9% ship readable HTML, 9.0% are empty shells, 4.1% land in the middle bucket.
Two honest limits on that chart. It measures whether HTML arrives, not how it got there, so prerendering and SSR both land in the first bar together. And it is a correlation across sites rather than a controlled test: a client-rendered app is usually a heavy JavaScript app, and that weight plausibly drives both the empty shell and the slow score. What it does establish is that shells are not a rare edge case at 9% of custom-built sites, and that they are the slowest group by a wide margin.
Effort and footprint, side by side
The honest cost comparison between migrating to SSR and the prerender setup we shipped. The prerender numbers are actuals from our codebase. The SSR numbers are a realistic estimate of migrating a Vite + React app of similar surface size.
| Dimension | SSR (Next.js / Remix) | Prerender (what we shipped) |
|---|---|---|
| Initial dev time | Weeks | 1-2 days |
| Files changed | Hundreds across routing, layouts, data hooks | 3 in the core path; a handful more for validation and route centralization |
| Production infrastructure | Node render server (often per region) | Static files served by the existing Express layer |
| Per-request CPU cost | Render every request | Zero. Send a file. |
| Build-time cost | Standard bundle build | +30-60 seconds for the headless browser pass |
| Cache strategy | Edge cache + revalidation rules | HTML on disk, no special cache layer |
| Failure modes you'll hit | Render server outages, hydration mismatches, render-time memory leaks | Route-list drift, missing API stubs, headless browser quirks |
| Reversibility | Hard. SSR changes data fetching everywhere. | Easy. Delete the script and the static.ts check. |
Effort estimates assume an existing Vite + React SPA of similar surface size to Stackra (around 42 indexable routes including the blog). Modern edge runtimes have lowered SSR's operational cost; the trade-off is real but smaller than it used to be.
How to know if this fits your site
Match the shape of your site to the rendering model. Use this as a starting point, not an absolute rule.
| Site shape | Prerender | SSR | Why |
|---|---|---|---|
| Marketing site (a few hundred routes) | Recommended | Overkill | Same HTML works for everyone. SSR's per-request value does not apply. |
| Blog with steady cadence | Recommended | Not needed | Deploy-per-change cadence is fine. Static files are the cheapest response. |
| Documentation site, frequent updates | Workable | Recommended | Hundreds of routes plus deploy-on-merge cadence stresses prerender build times. |
| E-commerce, hundreds of SKUs | Workable | Recommended | Either works. SSR wins if inventory or pricing changes between deploys. |
| E-commerce, tens of thousands of SKUs | Top SKUs only | Recommended | Build-time render of every variant is impractical. Hybrid often makes sense. |
| News, live events, hourly updates | Not viable | Recommended | Deploy-per-update is infeasible. Per-request rendering is the only honest answer. |
| Personalized or auth-aware indexable pages | Not viable | Recommended | HTML varies per visitor or per logged-in user. Per-request render needed. |
| SaaS product behind auth | Irrelevant | Irrelevant | Not indexable by design. Pick on operational preference; SEO is not the deciding factor. |
| Already on Next.js, Remix, or Nuxt | Available | One config flag | Migration cost is zero in either direction. |
"Recommended" means: this is the default unless you have a specific reason otherwise. "Workable" means: it can work, but the other column is usually less painful at this scale.
When prerendering is the wrong call, starting with ecommerce
Prerendering suits our site because every indexable page shows the same HTML to every visitor. Plenty of sites are not shaped like that, and an online store is the clearest example of the mismatch.
- •Regional pricing and currency. A brand selling into six markets needs different HTML per market, and a build cannot resolve which visitor is asking.
- •Live inventory. A prerendered product page can still say in stock after the last one sold, and serving a stale page is worse than a slower first byte.
- •Catalogue size. Five thousand products across six locales is thirty thousand build-time renders, repeated on every deploy.
- •Shipping and returns that change by destination. Same problem as pricing, and it usually sits right on the product page.
One thing worth clearing up, because it comes up every time this decision is discussed: server-side rendering does not make your pages heavier. The JavaScript bundle is the same either way. A client-rendered page ships an empty shell plus that bundle, a server-rendered page ships the content plus the same bundle, and the difference between them is a few kilobytes of markup. What SSR actually costs is time to first byte, because the page is assembled per request instead of being served from cache. That is the real trade to weigh, and payload size is not.
If you run a store, do not read our decision as a recommendation to prerender. We picked it because our marketing pages are identical for everyone. Yours are not, and per-request rendering is the honest answer for a catalogue that changes between deploys.
What we'd tell another small team
Two lessons that did not make it into the pain points:
- •Pick the rendering model that matches the shape of your site, not the framework with the loudest fan club. Most marketing surfaces do not need SSR.
- •Test like a crawler. The fastest way to find out what AI tools see is to run `curl` against your own pages and look at the raw response. If the headline is not there, the page is invisible.