Skip to main content
StackraStackra
Case Study
8 min readApril 20, 2026Verified against codebase

How Stackra Made a React App Visible to AI Without Next.js

Our marketing pages were invisible to AI crawlers. The industry default is to migrate to Next.js or Remix.

We picked a smaller fix on top of our existing Vite + Express setup. Here is the story, the numbers, and a decision framework you can apply to your own site.

Industry
B2B SaaS for small and mid-sized businesses
Stack
Vite + React (SPA), Express, Playwright, TypeScript
Outcome
Same content visible to humans and AI crawlers, no render server in production, two days of focused work.

At a glance

AI crawlers were seeing an empty HTML shell on every marketing page.

3
Files changed
in the core path; a handful more for validation
+30-60s
Build-time cost
captures every marketing route at build
0ms
Per-request render cost
production just serves static files
42
Indexable routes covered
13 marketing + 29 blog articles
Easy
Reversibility
delete the script and one server check
1-2 days
Initial dev time
vs weeks for an SSR framework migration

Three pain points, by site type

Tap the one that sounds like your site to read the full story.

1

AI tools see a blank page when they fetch your site

Affects: Any single-page React, Vue, or Angular app. Vite, Create React App, Gatsby in CSR mode, Nuxt without SSR, plain Webpack SPAs.

If this is you

If someone asks ChatGPT, Claude, or Perplexity about your business, the answer is built from what those tools can read in your raw HTML. If your homepage is a React SPA, that is almost nothing.

What it looks like

Run `curl -s https://yoursite.com | grep 'your headline'` and the headline is missing. The page is just a `<div id="root"></div>` and a script tag. AI crawlers reading that conclude your site has no content.

What we tried first

Nothing for a while. The site looked perfect in a browser, so the problem was invisible until we ran the same crawl an AI tool would.

What worked

A build-time step that visits every public page in a headless browser, waits for content to render, and saves the resulting HTML to disk. Production serves those static files directly. AI crawlers see the same content humans see.

Result

Every marketing page on stackra.app now returns full HTML to direct fetches. Verifiable with `curl.exe -s https://stackra.app/about/ | Select-String 'Stackra'`: you get matches. Try the same command on a site that has not solved this and you get nothing back. No error, just silence. That silence is what Google and every AI crawler sees too. To see what a crawler actually receives, drop the filter: `curl.exe -s https://yoursite.com/`. If the output is a single line with a script tag and a `<div id="root"></div>`, your content is invisible to bots. If you see headings, paragraphs, and real text, you are in good shape. One more thing to check in that output: look for `<meta name="robots" content="noindex,nofollow">`. That tag tells every crawler not to index the page and not to follow its links. It is easy to leave in by accident from a staging environment, and any page carrying it will not appear in search results or AI citations regardless of how good the content is.

2

Pages that load data render empty during the build

Affects: Any prerender setup where the build runs without your production API attached. Common with Astro, Gatsby, custom Vite builds, and headless-CMS sites that fetch at runtime.

If this is you

If your homepage shows live data (pricing, listings, stats, search results), prerendering it the naive way captures the loading spinner instead of the data. Crawlers index 'Loading...' and nothing else.

What it looks like

Our Benchmarks page tried to fetch from `/api/benchmarks/stats` during the build. The build server had no backend attached. Fetch failed. TanStack Query returned an empty state. We captured an 18KB shell with no grade bars, no scores, no industry data.

What we tried first

Two failed attempts. First we tried injecting JSON into the captured HTML after the fact: HTML is not React, so the data sat in a script tag and never made it into the page. Then we tried injecting data through Playwright's `addInitScript` so it was present before the page booted: TanStack Query treated the seeded data as stale and triggered a background refetch that timed out.

What worked

Add a single proxy route to the build-time static server. Any request to `/api/benchmarks/stats` during the build forwards to the live production API. Real data, real render, real captured HTML. We also considered intercepting the request inside the headless browser, or telling the data layer to never refetch. Both work. The proxy was cleanest because it matches what production actually does.

Result

The Benchmarks page now prerenders with full content: grade distributions, pillar averages, and platform breakdowns all present in the raw HTML.

3

New pages silently disappear from your prerender

Affects: Any setup where the route list is in a separate file from the router. Custom prerender scripts, sitemap generators, and any tool that needs to be told what to render.

If this is you

If your team adds new marketing pages over time, you will eventually deploy a page that works perfectly in the browser but is invisible to bots. The build does not warn you. The page just does not exist as far as crawlers are concerned.

What it looks like

We added seven marketing pages over a few months: the GEO page, four platform pages, privacy, and terms. Every one of them rendered fine in a browser. None of them were in the prerender route list. All seven were served as the SPA shell to anything that did not run JavaScript.

What we tried first

We caught this manually by running curl against each page and noticing the response was suspiciously small.

What worked

Centralize the route list in one place, then add a validation step at the end of every build that opens each captured file and checks three things: is it bigger than a minimum size, does it contain an `<h1>`, and does it contain the brand name. If any check fails, the build exits with an error.

Result

The build now catches missing routes and silent renders before they ship. A captured file under 5KB for a marketing page or under 3KB for a blog article fails the build.

About Stackra

Stackra is a free website audit tool for small and mid-sized businesses. 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. 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.

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.

SSR migration vs Stackra's prerender approach
DimensionSSR (Next.js / Remix)Prerender (what we shipped)
Initial dev timeWeeks1-2 days
Files changedHundreds across routing, layouts, data hooks3 in the core path; a handful more for validation and route centralization
Production infrastructureNode render server (often per region)Static files served by the existing Express layer
Per-request CPU costRender every requestZero. Send a file.
Build-time costStandard bundle build+30-60 seconds for the headless browser pass
Cache strategyEdge cache + revalidation rulesHTML on disk, no special cache layer
Failure modes you'll hitRender server outages, hydration mismatches, render-time memory leaksRoute-list drift, missing API stubs, headless browser quirks
ReversibilityHard. 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.

Platform / site-shape decision matrix
Site shapePrerenderSSRWhy
Marketing site (a few hundred routes)RecommendedOverkillSame HTML works for everyone. SSR's per-request value does not apply.
Blog with steady cadenceRecommendedNot neededDeploy-per-change cadence is fine. Static files are the cheapest response.
Documentation site, frequent updatesWorkableRecommendedHundreds of routes plus deploy-on-merge cadence stresses prerender build times.
E-commerce, hundreds of SKUsWorkableRecommendedEither works. SSR wins if inventory or pricing changes between deploys.
E-commerce, tens of thousands of SKUsTop SKUs onlyRecommendedBuild-time render of every variant is impractical. Hybrid often makes sense.
News, live events, hourly updatesNot viableRecommendedDeploy-per-update is infeasible. Per-request rendering is the only honest answer.
Personalized or auth-aware indexable pagesNot viableRecommendedHTML varies per visitor or per logged-in user. Per-request render needed.
SaaS product behind authIrrelevantIrrelevantNot indexable by design. Pick on operational preference; SEO is not the deciding factor.
Already on Next.js, Remix, or NuxtAvailableOne config flagMigration 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.

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.
SE
Stackra Engineering
Engineering team at Stackra
Last verified April 20, 2026
Claims verified against the live Stackra codebase. Specific file and line references are included in the methodology section.

Want to see what bots see on your site?