SvelteKit Integration
SvelteKit’s server hooks (hooks.server.ts) are the ideal integration point for SeoRend. The handle hook intercepts every request before it reaches your routes — perfect for bot detection and prerendering.
How It Works
Section titled “How It Works”Bot → SvelteKit handle hook → SeoRend API → Rendered HTMLUser → SvelteKit handle hook → Your SvelteKit AppRequirements
Section titled “Requirements”- SvelteKit project with
@sveltejs/adapter-nodeor any SSR adapter - SeoRend API key from app.seorend.com
-
Add your API key
Section titled “Add your API key”.env SEOREND_API_KEY=sk_live_your_key_hereFor production, set it in your hosting environment variables.
-
Create or update
Section titled “Create or update hooks.server.ts”hooks.server.tsCreate
src/hooks.server.ts(or add to your existing file):src/hooks.server.ts import type { Handle } from '@sveltejs/kit';const BOT_PATTERNS = [// Google'googlebot', 'googleother', 'storebot-google', 'google-inspectiontool','google-extended', 'google-cloudvertexbot', 'google-favicon','apis-google', 'adsbot-google', 'mediapartners-google', 'feedfetcher-google', 'google page speed',// Bing / Microsoft'bingbot', 'bingpreview', 'msnbot', 'adidxbot', 'microsoftpreview',// Yandex'yandexbot', 'yandeximages', 'yandexvideo', 'yandexmobilebot', 'yandexmetrika', 'yandexaccessibilitybot', 'yabrowser',// Baidu & other search'baiduspider', 'duckduckbot', 'duckassistbot', 'applebot', 'naver','sogouspider', '360spider', 'coccoc', 'seznambot', 'qwantbot','ecosia', 'yahoo', 'exabot', 'petalbot', 'bravebot',// AI / LLM crawlers'gptbot', 'oai-searchbot', 'chatgpt-user', 'chatgpt','claudebot', 'claude-web', 'claude-user', 'claude-searchbot', 'anthropic-ai','perplexitybot', 'perplexity-user', 'amazonbot', 'ccbot','meta-externalagent', 'meta-externalfetcher','mistralai-user', 'cohere-ai', 'ai2bot','diffbot', 'deepseekbot', 'firecrawlagent', 'kagi-fetcher','bytespider', 'tiktokspider','xai-crawler', 'timpibot', 'anchor browser', 'novellum ai crawl', 'proratainc',// Social media / link previews'facebookexternalhit', 'facebookcatalog', 'facebookbot', 'facebookplatform', 'instagram','twitterbot', 'linkedinbot', 'pinterestbot', 'pinterest','slackbot', 'slack-imgproxy', 'discordbot', 'telegrambot', 'whatsapp','redditbot', 'snapchat', 'vkshare','tumblr', 'flipboard', 'outbrain', 'embedly', 'bitlybot', 'quora link preview', 'line', 'viber',// SEO tools'semrushbot', 'splitsignalbot', 'ahrefsbot', 'ahrefssiteaudit','mj12bot', 'rogerbot', 'dotbot', 'chrome-lighthouse', 'screaming frog','oncrawlbot', 'botifybot', 'deepcrawl', 'lumar', 'dataforseobot', 'serpstatbot', 'w3c_validator',// Internet Archive'ia_archiver', 'archive.org_bot',];const STATIC_EXT = /\.(js|css|png|jpg|jpeg|gif|ico|svg|webp|avif|woff2?|ttf|eot|pdf|zip|mp4|wasm|map)$/i;function isBot(userAgent: string): boolean {const ua = userAgent.toLowerCase();return BOT_PATTERNS.some(p => ua.includes(p));}const API_KEY = process.env.SEOREND_API_KEY;export const handle: Handle = async ({ event, resolve }) => {const { request } = event;const ua = request.headers.get('user-agent') ?? '';const url = new URL(request.url);// Only intercept GET requests for non-static pathsif (request.method !== 'GET' ||STATIC_EXT.test(url.pathname) ||url.pathname.startsWith('/_app/') ||url.pathname.startsWith('/api/') ||request.headers.get('x-seorend-processed') === '1') {return resolve(event);}if (!isBot(ua) || !API_KEY) {return resolve(event);}try {const renderResponse = await fetch('https://render.seorend.com/v1/render', {method: 'POST',headers: {'Content-Type': 'application/json','Authorization': `Bearer ${API_KEY}`,},body: JSON.stringify({url: request.url,userAgent: ua,}),});if (!renderResponse.ok) {return resolve(event);}const data = await renderResponse.json();return new Response(data.html, {status: data.status ?? 200,headers: {'Content-Type': 'text/html; charset=utf-8','X-Seorend-Cache': data.cache?.hit ? 'HIT' : 'MISS',},});} catch {// Network error → serve normallyreturn resolve(event);}}; -
Composing with existing hooks
Section titled “Composing with existing hooks”If you already have a
handlehook, use SvelteKit’ssequencehelper:src/hooks.server.ts import { sequence } from '@sveltejs/kit/hooks';import type { Handle } from '@sveltejs/kit';const seorend: Handle = async ({ event, resolve }) => {// ... (seorend hook code from step 2)};const auth: Handle = async ({ event, resolve }) => {// ... your existing auth hookreturn resolve(event);};// SeoRend MUST come first — before auth, i18n, etc.export const handle = sequence(seorend, auth); -
Deploy
Section titled “Deploy”Build and start your app:
Terminal window npm run buildSEOREND_API_KEY=sk_live_... node buildOr use your preferred adapter (node, vercel, netlify, cloudflare).
Testing Locally
Section titled “Testing Locally”npm run dev# Should return X-Seorend-Cachecurl -s -I -A "Googlebot/2.1" http://localhost:5173/
# Should NOT have X-Seorend-* headerscurl -s -I -A "Mozilla/5.0 Chrome/120" http://localhost:5173/Troubleshooting
Section titled “Troubleshooting”Hook not running: ensure the file is at src/hooks.server.ts (not hooks.ts or hooks.client.ts).
process.env.SEOREND_API_KEY is undefined: the hooks.server.ts file runs on the server, so process.env works. But with Vite, you need to expose it — either use $env/static/private or ensure the variable is set in your server environment.
Using SvelteKit’s env system:
import { SEOREND_API_KEY } from '$env/static/private';Next Steps
Section titled “Next Steps”- Testing guide — end-to-end verification
- Warm Bot — automatically warm important pages