Prerender API - Win AI search
Prerender your CSR pages into HTML using Cloudflare Worker, Vercel or Netlify functions.
You must own the target domain (added to your LovableHTML account). The API verifies domain ownership before rendering.
Auth header
Send your API key with one of these headers:
x-lovablehtml-api-key: <API_KEY>Authorization: Bearer <API_KEY>
Create/manage keys in the dashboard.
Render endpoint
GET /api/prerender/render?url=<ENCODED_URL>
Headers (one of):
x-lovablehtml-api-key: <API_KEY>Authorization: Bearer <API_KEY>
Behavior
- If prerendering applies: returns
200 text/htmlwith the page HTML. - If prerendering does not apply (static asset, non-HTML request, or browser navigation): returns
304withLocationheader pointing to the target URL.
Notes
- Static assets (e.g.
.css,.js, images, fonts) are never prerendered. Follow theLocationheader or fetch directly. - To ensure HTML rendering, send
Accept: text/html. The endpoint classifies requests similarly to the built-in prerenderer.
Cloudflare Workers (run for every request via Route)
- If your domain is not on Cloudflare yet, add it first
- Set up the hostname in Lovable first, then make sure Cloudflare is proxying it.
- Most common case: if you are connecting
www,app, or another subdomain, point it to the hostname Lovable gives you using a proxied CNAME (orange cloud on). - If you are connecting the apex/root domain such as
yourdomain.com, follow the DNS records Lovable gives you for that hostname. In many cases that will be an A record plus verification records instead of a CNAME. - Create a new Worker → choose a "Hello World" Worker → Deploy → Edit Code
- Paste the snippet below and deploy your Worker
- Under Variables and Secrets, add a secret named
LOVABLEHTML_API_KEYor run:wrangler secret put LOVABLEHTML_API_KEY - Go to your Worker → Settings → Domains & Routes → Add Route → enter
yourdomain.com/* - Set Failure mode to Fail open (proceed) and save.
- Deploy the Worker. It can take a couple of minutes to start working.
Important: Use Routes, not Custom Domains
Lovable remains the origin for your site, so the Worker should be attached with a Route. Use Custom Domains only when the Worker is the origin. Keep the DNS record orange-clouded so traffic reaches Cloudflare before it reaches Lovable.
// lovablehtml-prerender.js (Cloudflare Worker)export default {async fetch(req, env) {// Only handle public GET navigationsif (req.method !== 'GET') return fetch(req);const isHtmlRequest = (req.headers.get('accept') || '').includes('text/html');if (!isHtmlRequest) return fetch(req);const headers = new Headers();headers.set('x-lovablehtml-api-key', env.LOVABLEHTML_API_KEY);headers.set('accept', 'text/html');const forward = ['accept-language','sec-fetch-mode','sec-fetch-site','sec-fetch-dest','sec-fetch-user','upgrade-insecure-requests','referer','user-agent',];for (const name of forward) {const v = req.headers.get(name);if (v) headers.set(name, v);}const r = await fetch('https://lovablehtml.com/api/prerender/render?url=' + encodeURIComponent(req.url), { headers });// 304 = not pre-rendered, pass through to originif (r.status === 304) {return fetch(req);}if ((r.headers.get('content-type') || '').includes('text/html')) {return new Response(await r.text(), { headers: { 'content-type': 'text/html; charset=utf-8' } });}return fetch(req);},};
Vercel Middleware (run before every request)
- Create
middleware.jsat the project root (same level aspackage.json). - Set the environment variable
LOVABLEHTML_API_KEYin your Vercel project settings if you did not paste a key into the snippet. - Deploy to Vercel. The middleware runs on the configured matcher for every request.
// middleware.js (place at the project root next to package.json)export const config = {// Use Node.js runtime to access standard Request/Responseruntime: 'nodejs',// Run on all paths except static assets (customize for your app)matcher: ['/((?!_some-static-path|favicon.ico).*)',// You can also be explicit:// '/:path*'],};import { next } from "@vercel/functions"; // <- npm install @vercel/functionsexport default async function middleware(request) {const isHtmlRequest = (request.headers.get("accept") || "").includes("text/html");// 2. If it's not a GET request or not HTML, pass through (e.g. API routes)if (request.method !== "GET" || !isHtmlRequest) {return next();}try {// Forward relevant headers and add custom onesconst headers = {"x-lovablehtml-api-key": <your-api-key>,accept: "text/html","accept-language": request.headers.get("accept-language") || "","sec-fetch-mode": request.headers.get("sec-fetch-mode") || "","sec-fetch-site": request.headers.get("sec-fetch-site") || "","sec-fetch-dest": request.headers.get("sec-fetch-dest") || "","sec-fetch-user": request.headers.get("sec-fetch-user") || "","upgrade-insecure-requests":request.headers.get("upgrade-insecure-requests") || "",referer: request.headers.get("referer") || "","user-agent": request.headers.get("user-agent") || "",};// Call LovableHTML prerender service with the full URLconst r = await fetch("https://lovablehtml.com/api/prerender/render?url=" +encodeURIComponent(request.url),{ headers });// not pre-rendered, regular browser routing - pass through to SPAif (r.status === 304) {return next();}// Return HTML or fall throughif ((r.headers.get("content-type") || "").includes("text/html")) {return new Response(r.body, {headers: { "content-type": "text/html; charset=utf-8" },});}} catch {// ignore}// Safety fallback: never block the requestreturn next();};
Netlify Edge Functions (attach to /*)
- Create the Edge Function file at
netlify/edge-functions/lovablehtml.js - Set the environment variable
LOVABLEHTML_API_KEYin your Netlify site settings if you did not paste a key into the snippet. - Deploy to Netlify. The edge function runs on every request and forwards bot traffic to the pre-rendering API.
// netlify/edge-functions/lovablehtml.js (Netlify Edge Function)export default async (request, context) => {// Only handle public GET navigationsconst isHtmlRequest = (request.headers.get('accept') || '').includes('text/html');if (request.method !== 'GET' || !isHtmlRequest) return context.next();const headers = {'x-lovablehtml-api-key': <your-api-key>,accept: 'text/html','accept-language': request.headers.get('accept-language') || '','sec-fetch-mode': request.headers.get('sec-fetch-mode') || '','sec-fetch-site': request.headers.get('sec-fetch-site') || '','sec-fetch-dest': request.headers.get('sec-fetch-dest') || '','sec-fetch-user': request.headers.get('sec-fetch-user') || '','upgrade-insecure-requests': request.headers.get('upgrade-insecure-requests') || '',referer: request.headers.get('referer') || '','user-agent': request.headers.get('user-agent') || '',};const r = await fetch('https://lovablehtml.com/api/prerender/render?url=' + encodeURIComponent(request.url), { headers });// 304 = not pre-rendered, pass through to originif (r.status === 304) {return context.next();}if ((r.headers.get('content-type') || '').includes('text/html')) {return new Response(await r.text(), { headers: { 'content-type': 'text/html; charset=utf-8' } });}return context.next();};export const config = {path: "/*",};
Errors
401 missing_api_key/invalid_api_key403 domain_not_owned200 text/htmlon success304withLocationheader when prerendering not applicable
Best practices
- Keep API keys secret; rotate/revoke when compromised.
- Always send
Accept: text/htmlfor bots/crawlers to maximize prerender chance.
Cache invalidation endpoints
These endpoints purge prerendered sources of pages for domains you own. Optionally prewarm to immediately re-render. Authentication is the same as the render endpoint (API key header).
POST /api/prerender/cache/invalidate-page-cache
Body:
{"domain": "example.com","path": "/pricing","prewarm": true}
Response:
{ "ok": true, "prewarmed": 1 }
Example:
curl -sS \-X POST \-H "content-type: application/json" \-H "x-lovablehtml-api-key: <API_KEY>" \-d '{"domain":"example.com","path":"/pricing","prewarm":true}' \https://<your-dashboard-host>/api/prerender/cache/invalidate-page-cache
POST /api/prerender/cache/invalidate-paths-cache
Body:
{"domain": "example.com","paths": ["/", "/pricing", "/blog/post"],"prewarm": true}
Response:
{ "ok": true, "prewarmed": 3 }
Example:
curl -sS \-X POST \-H "content-type: application/json" \-H "x-lovablehtml-api-key: <API_KEY>" \-d '{"domain":"example.com","paths":["/","/pricing","/blog/post"],"prewarm":true}' \https://<your-dashboard-host>/api/prerender/cache/invalidate-paths-cache
POST /api/prerender/cache/invalidate-site-cache
Body:
{ "domain": "example.com" }
Response:
{ "ok": true, "accepted": true }
Example:
curl -sS \-X POST \-H "content-type: application/json" \-H "x-lovablehtml-api-key: <API_KEY>" \-d '{"domain":"example.com","prewarm":true}' \https://<your-dashboard-host>/api/prerender/cache/invalidate-site-cache
Notes
- The API validates that the
domainbelongs to the authenticated user. prewarm: truedeletes the old cache and immediately re-renders the path(s).- Common variants (with/without trailing slash) are handled automatically.
API Collections & Integrations
Explore and test the Prerender API using these platforms:
- Postman Collection - Interactive API documentation with request examples
- APIDog Collection - Alternative API testing and documentation
- RapidAPI - Marketplace listing for the Prerendering API
