Encited logo
Pricing

How to Integrate Pre-rendering with Nuxt

Integrate pre-rendering with Nuxt Nitro middleware for client-rendered pages that need crawler-ready HTML without changing browser responses.

How Nuxt pre-rendering works

Nuxt can run Encited in Nitro server middleware for client-rendered routes that need complete HTML when crawlers request them.

Nitro middleware handles eligible crawler HTML requests before page handlers. Browser requests and render fallbacks continue through the Nuxt server.

Prerequisites

  • A Nuxt 3 deployment that runs Nitro server middleware.
  • Access to add server/middleware/encited-prerender.js.
  • LOVABLEHTML_API_KEY exposed through private Nuxt runtime configuration.

Set up pre-rendering with Nuxt

1

Create the Nitro middleware

Save the snippet as server/middleware/encited-prerender.js in a Nuxt 3 project.

2

Set the API key

Provide LOVABLEHTML_API_KEY in the server runtime and expose it as runtimeConfig.lovablehtmlApiKey in nuxt.config.ts. Nitro loads server middleware before page handlers.

3

Build and deploy Nuxt

Deploy the server output rather than a static-only export.

npm run build
encited-prerender.js
CopyDownload
// server/middleware/encited-prerender.js (Nuxt 3 / Nitro)
// nuxt.config.ts: runtimeConfig: { lovablehtmlApiKey: process.env.LOVABLEHTML_API_KEY }
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 default defineEventHandler(async (event) => {
const method = getMethod(event);
const accept = getHeader(event, 'accept') || '';
const userAgent = getHeader(event, 'user-agent') || '';
const isHtml = !accept || accept === '*/*' || accept.includes('text/html');
if (method !== 'GET' || !isHtml || !CRAWLER.test(userAgent)) return;
const publicUrl = getRequestURL(event).toString();
try {
const config = useRuntimeConfig(event);
const headers = {
'x-lovablehtml-api-key': config.lovablehtmlApiKey,
accept: 'text/html',
};
for (const name of ENCITED_FORWARD_HEADERS) {
const value = getHeader(event, name);
if (value) headers[name] = value;
}
const rendered = await fetch(
'https://encited.com/api/prerender/render?url=' + encodeURIComponent(publicUrl),
{ headers, redirect: 'manual' },
);
const location = rendered.headers.get('location');
if (rendered.status === 301 && location) return sendRedirect(event, location, 301);
if (rendered.status === 304) return;
if (rendered.status === 200 && (rendered.headers.get('content-type') || '').includes('text/html')) {
setResponseStatus(event, 200);
setResponseHeader(event, 'content-type', 'text/html; charset=utf-8');
for (const name of ENCITED_RESPONSE_HEADERS) {
const value = rendered.headers.get(name);
if (value) setResponseHeader(event, name, value);
}
return sendStream(event, rendered.body);
}
} catch {
// Returning lets Nitro continue to the normal route.
}
});

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

The Nuxt middleware never runs in production

Deploy the Nitro server output instead of a static-only generate target and confirm the file lives under server/middleware.

Nuxt cannot read the Encited API key

Map LOVABLEHTML_API_KEY to private runtimeConfig.lovablehtmlApiKey and redeploy the server runtime.

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 Nuxt server middleware?

Nitro middleware intercepts eligible crawler requests, returns Encited rendered HTML, and leaves browser rendering to the existing Nuxt application.

Can a static Nuxt export use this pre-rendering middleware?

No. Request middleware requires a running Nitro server or compatible serverless deployment.

Where is the Encited API key stored in Nuxt?

Keep it in private runtimeConfig sourced from LOVABLEHTML_API_KEY; do not expose it through public runtime configuration.

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