React
headless-prerender works with any client-rendered React app - Create React App, Vite, or a custom bundle - using React Router or any other router. No changes to your components are required.
When you need it
Your app renders into an empty root element:
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>Crawlers that do not run JavaScript see only that empty div. Prerendering gives them the rendered output instead.
Setup
Deploy your built app as usual (static files behind a CDN or web server).
Run the prerender service:
bashheadless-prerender serve \ --port 3000 \ --allow-origin https://www.example.com \ --cache-ttl 300000
Signalling "ready to snapshot"
By default the renderer waits for the network to go idle (networkidle2). If your app finishes rendering after that - lazy routes, delayed data - give it more time:
headless-prerender serve --extra-wait 500For a precise signal, expose a global flag once your app has painted its content, and render with a longer wait:
useEffect(() => {
// after the route's data has loaded
(window as any).__PRERENDER_READY__ = true;
}, [dataLoaded]);Then poll for it in a small wrapper around the library:
import { Renderer } from "headless-prerender";
const renderer = new Renderer({ waitUntil: "networkidle0", extraWaitMs: 200 });
const { html } = await renderer.render("https://www.example.com/page");(There is no built-in "wait for selector" option; use extraWaitMs or a proxy that retries.)
Correct status codes for 404s
An SPA usually serves index.html with a 200 for unknown routes. So bots can see a real 404, render a <meta name="prerender-status-code"> tag in your not-found route and have your proxy read it, or serve those routes from the server with the right status.
function NotFound() {
return (
<>
<meta name="prerender-status-code" content="404" />
<h1>Page not found</h1>
</>
);
}Meta tags and social previews
Set per-route <title> and <meta property="og:*"> tags with react-helmet-async or React 19 document metadata. The prerender captures them into the static HTML, so social scrapers get correct previews.
Checklist
- [ ]
--allow-originset to your site - [ ]
--extra-waittuned so late content is captured - [ ] Per-route
<title>/ Open Graph tags rendered client-side - [ ] 404 routes distinguishable (status-code meta tag or server route)
- [ ] Caching enabled