Back to News & Insights
SEO August 10, 2026 · 9 min read

Prerender is gone — a Playwright-based replacement

A maintained, self-hosted prerender server built on Playwright — an alternative to the abandoned prerender/prerender and the archived Rendertron. Renders JavaScript pages to static HTML for Googlebot and AI crawlers, with a migration guide from the old server.

Prerender is gone — a Playwright-based replacement

The npm package still resolves and still installs — last published September 2024, and still pulling close to 25,000 downloads a month — so existing deployments keep running and fresh installs keep working. What's gone is the path to a fix. No repository means no security patches, no issue tracker, and no way to close the one crash-recovery gap in its own design: if Chrome dies twice within about a second, the old server calls process.exit() and relies on an external supervisor to bring it back, with no retry loop of its own.

So I wrote renderready, a self-hosted prerendering server built on Playwright and headless Chromium. Two runtime dependencies, TypeScript throughout, and a migration guide from the old server that is mostly a URL change and a handful of renames.

It didn't start as a package. The rendering core came out of a production deployment that serves over 200,000 pages a day, and renderready is that core extracted, cleaned up and published on its own. The deployment doesn't run the package verbatim — it wraps the same renderer in infrastructure I'll come back to at the end, because the deployment shape matters more than the package does.

That's the whole quickstart. The rest of this post is why the interesting parts work the way they do.

The short version, if you're deciding whether to keep reading: What it does. Takes a client-rendered page — React, Angular, Vue, anything that builds its DOM in the browser — runs it in headless Chromium, and hands back the finished HTML. Crawlers that don't execute JavaScript get your actual content instead of an empty . What it replaces. prerender/prerender (repository gone), Rendertron (archived 2022), Rendora (archived 2025). What it costs you. Two runtime dependencies, MIT, Node 22.12+, three functions and a CLI. Migrating from the old server is a URL change and a handful of renames. What it isn't. Not a hosted service, not a cache, not an SSR framework, and not a reason to skip SSR if SSR is available to you.

If you're picking an architecture today, prerendering is not your first choice and I'm not going to pretend otherwise. Server-side rendering or static generation gives crawlers and users the same HTML, which is strictly better than maintaining a second rendering path. Google says as much in its own docs: dynamic rendering is a bridge, not a destination.

Prerendering earns its place when you can't take that route: A large Angular or Vue SPA that predates the frameworks that made SSR easy, where a rewrite is a quarter of engineering time nobody has approved. A build pipeline you don't own, where "add SSR" means renegotiating with another team. Crawlers that don't execute JavaScript at all — which, as of the last two years, is most of the ones that are new. Vercel's analysis of over 500 million GPTBot fetches found no evidence of JavaScript execution, and the same holds for ClaudeBot, PerplexityBot and Bytespider: they read the raw HTML and leave. Googlebot, Gemini and AppleBot render. Nothing else does. SSR fixes this too, obviously — prerendering fixes it without a rewrite. Social crawlers. Even a site with perfect SSR often wants a renderer for Open Graph consumers that execute no JavaScript at all. Scraping and content extraction, which has nothing to do with SEO but needs exactly the same machinery.

If none of that describes you, use Next.js or Nuxt and close the tab. If some of it does, you need a renderer, and the options have quietly rotted.

| Option | Status | Notes | | --- | --- | --- | | prerender/prerender | Repository gone (404) | npm package still installs; no patches, no issues, no fixes | | Rendertron | Archived October 2022 | Google's own; README says deprecated | | Rendora | Archived January 2025 | Read-only. 2k stars, and the last release was December 2018 | | prerender.io | Commercial | From $49/month; hosted, so no infrastructure to run | | renderready | New | What this post is about |

Every self-hosted option in that table is either archived or has had its repository deleted. Rendora is the clearest illustration: 2,000 stars, a genuinely good architecture, one release in 2018, archived by its owner seven years later. The category didn't get solved — it got abandoned, because the frameworks moved on and everyone assumed the problem went with them.

It didn't. The apps that needed prerendering in 2019 are still in production, and they still need it.

The other thing the survivors have in common is that they wrap Chrome over hand-rolled DevTools Protocol calls. Playwright already solved browser lifecycle management properly, and building on it removes most of what made the old servers fragile.

You navigate to a URL. At some point the app has fetched its data, rendered its DOM, and the HTML is worth capturing. Capture too early and you serialize a spinner. Capture too late and every render costs you the full timeout.

The obvious answer is Playwright's networkidle, and it's a trap. Any app with long-polling, streaming, a keep-alive connection, a live chat widget, or an analytics beacon on an interval never reaches network idle. Using it doesn't make renders slow — it pins every single render at the timeout ceiling. The standard advice you'll find is "wait for a specific element instead," which is fine advice for a test suite and useless here, because you're rendering pages you don't control and don't have a selector for.

Network quiet. No requests in flight, and none started or settled for waitAfterLastRequest — 500ms by default. This is the only signal available for a site you haven't instrumented, so it's the fallback rather than the exception. WebSocket and EventSource requests are excluded from the in-flight count outright, because they never finish and counting them means never going quiet.

Nothing is captured until it turns true. Once it does, capture happens as soon as the network goes quiet or renderReadyDelay (1000ms) elapses, whichever comes first — so an app that knows it's finished can cut a render short instead of waiting out its own trailing analytics requests. Never define the flag and nothing breaks; network quiet handles it.

And a timeout is not an error. You get whatever had rendered, timedOut: true, and an x-renderready-timed-out header, because a partial capture is usually more useful to a crawler than a 504.

A prerenderer that returns 200 for everything quietly poisons your index. A soft 404 gets indexed as a real page; a moved URL never updates. So the response carries the origin's status code, and the page can override it from :

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation