
Prerendering: How to Turn a React SPA Into a Google-Friendly Site
TL;DR: „Prerendering renders your SPA to static HTML at build time – with JSON-LD, meta tags and edge deploy. You keep SPA DX, but Google sees a real page."
— Till FreitagThe Problem: SPAs Are a Blank Page for Crawlers
A React SPA serves an almost empty <div id="root"></div> on the first request. The real content only appears after JavaScript is loaded, parsed and executed. Fine for users – but for crawlers, social-sharing previews and LLM bots the game is often over by then.
We covered the why in Vibe Coding SEO. This article goes one layer deeper: How do you prerender a modern React/Vite SPA without switching to Next.js or running an SSR server?
The Solution: Build-Time Prerendering With Playwright
Instead of rendering every request live on a Node server, we do it once – at build time:
- Vite builds the SPA into static JS/CSS bundles as usual
- A Playwright headless browser starts a local server and visits every route
- The fully hydrated HTML is written per route as
index.html - JSON-LD, meta tags and sitemap are injected into the HTML stream
- The entire static output is deployed to the Vercel Edge Network
Result: crawlers get full HTML in a single request. Users get the SPA experience as soon as JS hydrates.
The Pipeline at a Glance
React SPA → Playwright → Static HTML → Schema.org → Edge Deploy → Crawler
(Vite) (Headless) (per route) (JSON-LD) (Vercel) & UserFour building blocks, deterministically chained. No plugin roulette, no PHP, no "works on my machine".
Why Not Just Use Next.js?
Fair question. Next.js solves the same problem – with a different trade-off:
| Aspect | Next.js SSR | Playwright SSG |
|---|---|---|
| Hosting | Node server / edge functions | Pure static files |
| Latency | Cold starts possible | Pure CDN, < 50 ms |
| Build complexity | Framework conventions | Vite + Playwright script |
| Vendor lock-in | High (Vercel-optimized) | None |
| Fit for Vibe Coding | Hard (conventions) | Ideal (standard React) |
For AI-native web apps with Lovable the standard React SPA is the faster path. Prerendering sits cleanly on top, without forcing the agent to learn framework conventions.
The Critical Details
1. Discover Routes
We read all routes from the React Router tree and add dynamic paths from markdown frontmatter (blog posts, events). A script produces the complete route list – single source of truth.
2. Inject Meta Tags at Build Time
react-helmet-async sets meta tags client-side – which is often not enough for crawlers. Our Vite plugin inject-seo reads the fully rendered <head> from the Playwright output and writes it as static markup into index.html. Bots see <title>, <meta>, OpenGraph and canonical before JS executes.
3. JSON-LD Per Page Type
Every page gets the matching schema: Organization for the homepage, Article for blog posts, Event for event pages, Product for tool pages. More in our SEO master strategy.
4. Sitemap & robots.txt
From the route list we generate sitemap.xml in parallel, with lastmod from frontmatter. Crawlers know every page; every change is visible.
5. Edge Deploy
Vercel distributes the static output directory to 100+ regions. TTFB under 50 ms worldwide – no caching plugin, no CDN configuration, no sysadmin.
What to Avoid
useEffectfor SEO-relevant data: runs only after hydration, crawlers never see it. Load data at build time or directly in JSX.- Client-only routing tricks: hash routes (
#/about) are not prerendered. Always use real History API routing. - Race conditions in Helmet: when multiple
<Helmet>instances fight, Playwright captures whichever wins last. A central<SEOHead>component avoids this.
What You Gain
- Lighthouse 95+ out of the box – without a performance plugin
- Full crawler access – Google, Bing, ChatGPT, Perplexity see everything
- Social previews work – OG image, title, description before JS
- No server maintenance – static files on the edge
- Full Vibe Coding speed – the agent builds standard React, the pipeline handles the rest
Conclusion: Prerendering Is the Missing Bridge
WordPress ships HTML because PHP renders server-side – with all the maintenance and plugin issues that come with it. An SPA ships no HTML because it renders client-side. Prerendering combines the best of both worlds: HTML-first for crawlers, SPA DX for the team.
This exact pipeline runs behind this site. We also show it live at TokenMade in Hamburg – no slides, in 5 minutes.








