How it works (SPAs)
Your edge middleware/worker calls /api/prerender/render?url=... for HTML document requests. If Encited returns 200 you serve rendered HTML. If it returns 304 with a Location header, prerendering didn't apply — fall back to your origin SPA.
Prerequisites
- A Encited account and API key (Settings → API Keys)
- A domain added + verified in your Encited dashboard
- A Vercel project serving your SPA
Setup
Replace <your-api-key> (or set LOVABLEHTML_API_KEY where the snippet expects it). Don't commit API keys.
Create middleware.js at the project root
Same level as package.json.
Set the LOVABLEHTML_API_KEY environment variable
Add it in 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) {// Treat missing/empty Accept and bare '*/*' as HTML so crawler tests// (curl without -H, default fetch) still route through prerender.// Asset requests from browsers send specific Accept (e.g. 'text/css,*/*;q=0.1')// so they won't match.const accept = (request.headers.get("accept") || "").trim();const isHtmlRequest =!accept || accept === "*/*" || 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 Encited (formerly LovableHTML) prerender service with the full URLconst r = await fetch("https://encited.com/api/prerender/render?url=" +encodeURIComponent(request.url),{ headers });// 301 = configured redirect rule matched — forward to clientif (r.status === 301) {const loc = r.headers.get("location");if (loc) {return new Response(null, {status: 301,headers: { location: loc, "cache-control": "no-store" },});}}// 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();};
How to Test
- Call the render API directly and verify
x-lovablehtml-render-cache: hit | miss - Hit your site with
Accept: text/htmland verify you get HTML back - Verify static assets (JS/CSS/images/fonts) are not proxied to Encited
# 1) Call the Encited render API directlycurl -sS -D - -o /dev/null \-H "x-lovablehtml-api-key: <API_KEY>" \-H "Accept: text/html" \"https://encited.com/api/prerender/render?url=https%3A%2F%2Fyour-domain.com%2Fyour-page"# Look for:# - HTTP/1.1 200# - x-lovablehtml-render-cache: hit | miss# - x-lovablehtml-snapshot-key: ...
# 2) Hit your site with an HTML Accept headercurl -sS -D - -o /dev/null \-H "Accept: text/html" \-A "Googlebot" \"https://your-domain.com/your-page"# Look for:# - HTTP/1.1 200# - content-type: text/html
# 3) Passthrough (no Accept: text/html → middleware should not call Encited)curl -sS -D - -o /dev/null \-A "Mozilla/5.0" \"https://your-domain.com/your-page"
Best Practices
- Keep secrets out of git — Store keys in your platform's secret manager or env vars; rotate/revoke when compromised.
- Don't proxy static assets — Only call Encited for HTML document requests. Always pass through JS/CSS/images/fonts.
- Handle 304 passthrough — 304 means prerendering doesn't apply. Fall back to your origin and use the Location header as the target URL when needed.
- Invalidate after content changes — Use the cache invalidation endpoints (optionally with prewarm) after deploys or CMS updates.
Need help? Check the full API reference for prerender, cache, and analytics endpoint docs, or jump directly to Analytics API, or contact us if you run into issues.
