Encited logo
Pricing

How to Integrate Pre-rendering with Express.js

Integrate pre-rendering with Express.js middleware so eligible crawler requests receive rendered HTML before your application routes run.

How Express pre-rendering works

Express.js can run Encited before application routes so crawler requests receive rendered HTML without changing responses for normal visitors.

The Express middleware handles eligible crawler GET requests for HTML. Browser visits and unsuccessful render calls continue through next() to the existing route stack.

Prerequisites

  • Node.js 18 or later so the middleware can use the native fetch API.
  • Access to register middleware before authentication, static files, and application routers.
  • LOVABLEHTML_API_KEY configured in the production process environment.

Set up pre-rendering with Express

1

Create the middleware file

Save the snippet as encited-prerender.js. It uses the native fetch API available in Node.js 18 and later.

2

Register it before your routes

Import encitedPrerender and call app.use(encitedPrerender) before authentication, static-file, and application routes.

3

Deploy or restart Express

Set LOVABLEHTML_API_KEY in your production environment, then restart the process.

npm start
encited-prerender.js
CopyDownload
// encited-prerender.js - register before your routes
const CRAWLER = /(?:(OAI-SearchBot|ChatGPT-User(?:\/\d+\.\d+)?|GPTBot|ChatGPT|OpenAI-))|(?:(anthropic-ai|ClaudeBot|claude-web|Claude-Web|Claude-User|Claude-SearchBot))|(?:(GrokBot|xAI-Grok))|(?:(PerplexityBot|Perplexity-User))|(?:(Google-InspectionTool|GoogleOther(?:-Image|-Video)?|Googlebot|Google-CloudVertexBot|Storebot-Google))|(?:(Microsoft-Copilot|CopilotBot|Copilot-Chat|Microsoft-CopilotPlugin))|(?:(BingBot|bingbot|BingPreview))|(?:facebookexternalhit.*Twitterbot|Twitterbot.*facebookexternalhit)|(?:(Meta-ExternalAgent|meta-externalagent|meta-externalfetcher|MetaBot|FacebookBot|facebookexternalhit))|(?:LinkedInBot)|(?:Amazonbot)|(?:(Applebot-Extended|Applebot))|(?:Bytespider)|(?:(DuckAssistBot|DuckDuckBot))|(?:(cohere-training-data-crawler|cohere-ai|CohereAI))|(?:MistralAI-User)|(?:AI2Bot)|(?:CCBot)|(?:Diffbot)|(?:omgili)|(?:TimpiBot)|(?:YouBot)|(?:(Bravebot|Brave-Search))|(?:Yandex)|(?:Twitterbot)|(?:Discordbot)|(?:(Slackbot|Slack-ImgProxy))|(?:TelegramBot)|(?:(Pinterest|Pinterestbot))|(?:Snapchat)|(?:Redditbot)|(?:Tumblr)|(?:(Mastodon|http\.rb))|(?:Viber)|(?:^Line\/)|(?:WhatsApp)|(?:(SkypeUriPreview|Skype))|(?:(Teams|MicrosoftPreview))|(?:Zoom)|(?:Notion)|(?:(Quora|QuoraBot|Quora-Bot|Poe-Bot))|(?:Medium)|(?:(Pocket|PocketParser|PocketImageCache))|(?:(Flipboard|FlipboardProxy))|(?:(Embedly|embed\.ly))|(?:(Baiduspider|Baidu))|(?:(Sogou|sogou))|(?:(Yeti|NaverBot|Naver))|(?:SeznamBot)|(?:(Qwantify|Qwant))|(?:Ecosia)|(?:MojeekBot)|(?:Mail\.RU_Bot)|(?:Yahoo! Slurp)|(?:(SemrushBot|SEMrush))|(?:(AhrefsBot|AhrefsSiteAudit))|(?:(DotBot|Rogerbot|moz\.com))|(?:MJ12bot)|(?:Screaming Frog)|(?:SISTRIX)|(?:SEOkicks)|(?:serpstatbot)|(?:RSiteAuditor)|(?:LvHTML-SEOAuditBot)|(?:Barkrowler)|(?:HubSpot Crawler)|(?:(AwarioSmartBot|AwarioBot))/i;
const ENCITED_FORWARD_HEADERS = [
'accept-language', 'sec-fetch-mode', 'sec-fetch-site', 'sec-fetch-dest',
'sec-fetch-user', 'upgrade-insecure-requests', 'referer', 'user-agent',
];
const ENCITED_RESPONSE_HEADERS = [
'x-lovablehtml-render-cache', 'x-lovablehtml-snapshot-key', 'cache-control',
'etag', 'content-digest', 'signature-input', 'signature',
];
export async function encitedPrerender(req, res, next) {
const accept = req.get('accept') || '';
const isHtml = !accept || accept === '*/*' || accept.includes('text/html');
if (req.method !== 'GET' || !isHtml || !CRAWLER.test(req.get('user-agent') || '')) {
return next();
}
const publicUrl = req.protocol + '://' + req.get('host') + req.originalUrl;
try {
const headers = {
'x-lovablehtml-api-key': process.env.LOVABLEHTML_API_KEY,
accept: 'text/html',
};
for (const name of ENCITED_FORWARD_HEADERS) {
const value = req.get(name);
if (value) headers[name] = value;
}
const rendered = await fetch(
'https://encited.com/api/prerender/render?url=' + encodeURIComponent(publicUrl),
{ headers, redirect: 'manual' },
);
if (rendered.status === 301 && rendered.headers.get('location')) {
return res.redirect(301, rendered.headers.get('location'));
}
if (rendered.status === 304) return next();
if (rendered.status === 200 && (rendered.headers.get('content-type') || '').includes('text/html')) {
for (const name of ENCITED_RESPONSE_HEADERS) {
const value = rendered.headers.get(name);
if (value) res.set(name, value);
}
res.status(200).type('html').send(Buffer.from(await rendered.arrayBuffer()));
return;
}
} catch {
// Fail open: keep serving the application from your existing route.
}
return next();
}
// app.use(encitedPrerender);

Test the integration

1. Send a crawler request

curl -sS -D - -o /dev/null \ -A "Googlebot" \ -H "Accept: text/html" \ "https://your-domain.com/"

Look for a successful HTML response and the x-lovablehtml-render-cache header.

2. Confirm browser passthrough

curl -sS -D - -o /dev/null \ -A "Mozilla/5.0" \ -H "Accept: text/html" \ -H "Accept-Language: en-US" \ -H "Sec-Fetch-Mode: navigate" \ -H "Sec-Fetch-Dest: document" \ -H "Upgrade-Insecure-Requests: 1" \ "https://your-domain.com/"

This request should be served by your existing website without an Encited render-cache header.

Common errors

Crawler requests reach Express routes before Encited

Register encitedPrerender with app.use before static files, authentication middleware, and every application router.

The render call returns unauthorized

Set LOVABLEHTML_API_KEY in the same production process that starts Express, then restart the process.

Request and security behavior

  • Only eligible GET requests for public HTML pages are considered for pre-rendering.
  • Along with the public page URL, only the API key and crawler-classification headers are sent to Encited.
  • Cookie and Authorization headers are never forwarded to Encited.
  • If Encited is unavailable or does not return rendered HTML, the request falls through to your website.

Common questions

How does pre-rendering work with Express.js middleware?

The middleware checks public HTML GET requests, calls Encited for recognized crawlers, and calls next() for normal visitors and fallback responses.

Where should Express.js pre-rendering middleware be registered?

Register it before authentication, static-file middleware, API routers, and SPA fallback routes so eligible crawler requests are intercepted first.

Will Express.js pre-rendering affect signed-in users?

No. Browser-classified requests continue through the normal Express middleware stack, and visitor cookies are not forwarded to Encited.

Avatar
How can we help?
Get instant answers to your questions or leave a message for an engineer will reach out
Ask AI about Encited
See our docs
Contact support
Leave a message
We'll get back to you soon
Avatar
Ask AI about Encited
Team is also here to help
Thinking
Preview
Drop an image to attach
Powered by ReplyMaven