Back to blogPrerender API - Win AI search

Prerender API - Win AI search

10/13/2025·by LovableHTML

Prerender your CSR pages into HTML using Cloudflare Worker, Vercel or Netlify functions.

You must own the target domain (connected in the dashboard). 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/html with the page HTML.
  • If prerendering does not apply (static asset, non-HTML request, or browser navigation): returns 304 with Location header pointing to the target URL.

Notes

  • Static assets (e.g. .css, .js, images, fonts) are never prerendered. Follow the Location header 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)

  1. If you do not have your domain connected to Cloudflare, add your domain to Cloudflare first.Cloudflare documentation.
  2. Sign up or Login to Cloudflare and visitWorkers dashboard.
  3. Click "Create Application" top right and select "Drag and drop your files" option.
  4. Download the snippet below and upload it to the Worker, hit "Deploy".
  5. Go to settings, hit plus on Domains & Routes, select your domain from zones dropdown and add a new route with (yourdomain.com/*).
lovablehtml-prerender.ts
CopyDownload
// Cloudflare Worker: attach to your zone to run on every request (yourdomain.com/*)
export default {
async fetch(req, env) {
// Only handle public GET navigations
if (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.PRERENDER_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 origin
if (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)

  1. Create middleware.ts at the project root (same level aspackage.json).
  2. Deploy to Vercel. The middleware runs on the configured matcher and will call the API and return HTML or redirect.
middleware.ts
CopyDownload
// middleware.ts (place at the project root next to package.json)
export const config = {
// Use Node.js runtime to access standard Request/Response without Next.js helpers
runtime: 'nodejs',
// Run on all paths except Next static assets; exclude API & dashboard here, too
matcher: [
'/((?!_some-static-path|favicon.ico).*)',
// You can also be explicit:
// '/:path*'
],
};
import { next } from "@vercel/functions"; // <- npm install @vercel/functions
export default async function middleware(request: 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();
}
// Forward relevant headers and add custom ones
const headers: Record<string, string> = {
"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 URL
const r = await fetch(
"https://lovablehtml.com/api/prerender/render?url=" +
encodeURIComponent(request.url),
{ headers }
);
// not pre-rendered, regular browser routing - pass through to SPA
if (r.status === 304) {
return next();
}
// Return HTML or fall through
if ((r.headers.get("content-type") || "").includes("text/html")) {
return new Response(r.body, {
headers: { "content-type": "text/html; charset=utf-8" },
});
}
};

Netlify Edge Functions (attach to /*)

  1. Add an Edge Function at netlify/edge-functions/lovablehtml.ts to run on every request and forward headers.
prerender.ts
CopyDownload
// netlify/edge-functions/lovablehtml.ts (Edge Function - new syntax)
import type { Config, Context } from "https://edge.netlify.com";
export default async (request: Request, context: Context) => {
// Only handle public GET navigations
const isHtmlRequest = (request.headers.get('accept') || '').includes('text/html');
if (request.method !== 'GET' || !isHtmlRequest) return context.next();
const headers: Record<string, string> = {
'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 origin
if (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: Config = {
path: "/*",
};

Errors

  • 401 missing_api_key / invalid_api_key
  • 403 domain_not_owned
  • 200 text/html on success
  • 304 with Location header when prerendering not applicable

Best practices

  • Keep API keys secret; rotate/revoke when compromised.
  • Always send Accept: text/html for 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:

language-json.json
CopyDownload
{
"domain": "example.com",
"path": "/pricing",
"prewarm": true
}

Response:

language-json.json
CopyDownload
{ "ok": true, "deleted": 1, "prewarmed": 1 }

Example:

language-bash.bash
CopyDownload
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:

language-json.json
CopyDownload
{
"domain": "example.com",
"paths": ["/", "/pricing", "/blog/post"],
"prewarm": true
}

Response:

language-json.json
CopyDownload
{ "ok": true, "deleted": 3, "prewarmed": 3 }

Example:

language-bash.bash
CopyDownload
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:

language-json.json
CopyDownload
{ "domain": "example.com" }

Response:

language-json.json
CopyDownload
{ "ok": true, "deleted": 42 }

Example:

language-bash.bash
CopyDownload
curl -sS \
-X POST \
-H "content-type: application/json" \
-H "x-lovablehtml-api-key: <API_KEY>" \
-d '{"domain":"example.com"}' \
https://<your-dashboard-host>/api/prerender/cache/invalidate-site-cache

Notes

  • The API validates that the domain belongs to the authenticated user.
  • prewarm: true deletes 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: