Prerendering with Rendertron vs Prerender.io
When a client-side app cannot be rewritten to render on the server yet, prerendering serves crawlers a fully rendered HTML snapshot while users still get the live app. The two established tools for this are self-hosted Rendertron and managed Prerender.io. This guide is part of dynamic rendering and bot prerendering and compares them on cost, maintenance, and cache control so you can pick the right bridge β and know when to skip both.
Problem Statement
A pure CSR route depends on the second-wave render, and until you can move it to SSR you need crawlers to receive real HTML. Prerendering solves this with a headless Chromium that renders each route once, caches the result, and returns that snapshot to bots. The decision is not whether to prerender but who runs the renderer: you (Rendertron) or a vendor (Prerender.io). That single choice cascades into cost, scaling, cache freshness, and how much of the failure surface you own.
Both tools sit behind the same middleware pattern β detect a crawler, route it to the prerendered snapshot, send users to the live app. The split is everything downstream of that fork, as the comparison below shows.
Step-by-step fix
-
Confirm prerendering is the right bridge, not the destination. If you can server-render or statically generate the route, do that instead β it serves one document to everyone and sidesteps the cloaking risk. Prerendering is for routes you cannot yet migrate, as framed in is dynamic rendering still recommended.
-
Add the crawler-detection middleware. Both tools share this shape: forward crawler requests to the prerender source, pass users through. Keep the bot list current and cache the snapshot.
// Express-style middleware, tool-agnostic const BOT = /googlebot|bingbot|yandex|duckduckbot|slurp|baiduspider/i; export function prerenderMiddleware(prerenderBase: string) { return async (req, res, next) => { const ua = req.headers["user-agent"] ?? ""; if (!BOT.test(ua)) return next(); // users get the live app const target = `${prerenderBase}/render?url=${encodeURIComponent(fullUrl(req))}`; const snapshot = await fetch(target); res.status(snapshot.status).set("content-type", "text/html").send(await snapshot.text()); }; } -
Option A β self-host Rendertron. Run the container yourself and point the middleware at it. You own scaling, memory, and the render cache, and you pay only for the compute.
# docker-compose.yml β self-hosted Rendertron services: rendertron: image: ghcr.io/your-org/rendertron:latest ports: ["3000:3000"] environment: CACHE: "datastore" # cache snapshots to avoid re-rendering RENDER_TIMEOUT: "10000" # abort slow renders deploy: resources: limits: memory: 1G # headless Chromium is memory-hungry -
Option B β use managed Prerender.io. Point the middleware at the vendor and let it cache and scale. You trade a recurring fee for not operating a render farm, and configure re-crawl frequency in the dashboard.
// Managed: the base URL is the vendor endpoint; add your token app.use(prerenderMiddleware("https://service.prerender.io")); // Set the token header the vendor expects, and configure cache TTL in their dashboard. -
Guarantee the snapshot matches the live render. Whichever tool you pick, the prerendered HTML must equal what the browser produces after hydration. Drift between the two is what turns dynamic rendering into cloaking, so schedule re-renders whenever content changes and diff periodically.
The choice comes down to who owns each part of the render path. The matrix below lines up the two tools on the axes that actually differ once the shared middleware is in place.
Validation
-
Fetch as a bot and as a user.
curl -A "Googlebot" https://example.com/product/42must return full HTML;curl -A "Mozilla/5.0" https://example.com/product/42returns the CSR shell. Both must describe the same content. -
Diff snapshot against live render. Render the route in a headless browser and compare its text to the prerendered snapshot; they should match. A CI assertion catches drift:
console.assert( snapshotText.trim() === liveRenderText.trim(), "Prerender snapshot diverged from the live render β cloaking risk", ); -
Check snapshot freshness. Confirm the cache re-renders after a content change; a stale snapshot indexes outdated content.
-
Inspect in Search Console. URL Inspectionβs rendered HTML should show the snapshot content and a self-referential canonical, consistent with the audit workflow.
Common Pitfalls
- Treating prerendering as permanent. It is a bridge; the longer a bot-only path runs, the more it drifts from the live app. Plan a move to SSR or static generation.
- Under-provisioning Rendertron memory. Headless Chromium is memory-hungry; too little RAM causes render timeouts and empty snapshots served to bots.
- Unbounded Prerender.io cost. Per-URL pricing balloons on large or frequently changing sites. Cap re-render frequency and cache aggressively.
- A stale bot list. Missing a crawler user agent means that bot gets the empty CSR shell. Keep the detection list current.
- Snapshot and live render diverging. Different content to bots and users is cloaking. Re-render on content change and diff regularly.
Cost is the pitfall that flips with scale. Managed per-URL pricing is cheap on a small site but overtakes self-hosted compute once URL and re-render volume climbs, so the right tool depends on where you sit relative to the break-even point below.
Frequently Asked Questions
Is Rendertron or Prerender.io better for a CSR app?
Neither is universally better. Rendertron is self-hosted and free of per-render fees but you run and scale the headless Chromium yourself. Prerender.io is a managed service with caching and a lower operational burden but a recurring cost that scales with URLs and re-renders. Choose Rendertron when you have infrastructure capacity and want control, and Prerender.io when you want to avoid maintaining a render farm.
Is prerendering for bots the same as cloaking?
It is only safe when the prerendered HTML matches what a userβs browser would render after JavaScript runs. Serving materially different content to bots is cloaking. Because a bot-only render path can drift from the live app over time, Google now treats this kind of dynamic rendering as a workaround rather than a recommended long-term solution.
Are there modern alternatives to Rendertron and Prerender.io?
Yes. Server-side rendering, static generation, and edge rendering serve the same HTML to users and bots and avoid the cloaking risk entirely. If you need a prerender-style snapshot, a small Puppeteer service on serverless functions or an edge runtime can replace both tools. Prerendering is best treated as a bridge while you migrate toward one of these.
Related
- Dynamic Rendering & Bot Prerendering β the section overview and where prerendering fits today.
- Is Dynamic Rendering Still Recommended by Google? β why prerendering is a bridge, not a destination.
- Set Up Prerendering for a React SPA β a concrete prerendering implementation.
- Edge-Side Rendering with Cloudflare Workers β the modern alternative that serves one document to all.
β Back to Dynamic Rendering & Bot Prerendering