Node.js / Express Integration
This guide covers integrating SeoRend into any Node.js server — Express, Fastify, Koa, or a plain http server. The same middleware logic works across all of them.
How It Works
Section titled “How It Works”The middleware checks every incoming GET request:
- Bot detected → forwards to SeoRend, returns pre-rendered HTML
- Regular user → passes request to your app unchanged
Requirements
Section titled “Requirements”- Node.js 18+
- SeoRend API key from app.seorend.com
-
Install dependencies
Section titled “Install dependencies”Terminal window npm install express -
Add the middleware
Section titled “Add the middleware”Create
seorend.middleware.js:seorend.middleware.js const SEOREND_API_URL = 'https://render.seorend.com/v1/render';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(ua) {if (!ua) return false;const lower = ua.toLowerCase();return BOT_PATTERNS.some(p => lower.includes(p));}module.exports = function seorend(options = {}) {const apiKey = options.apiKey || process.env.SEOREND_API_KEY;if (!apiKey) throw new Error('SEOREND_API_KEY is required');return async function(req, res, next) {const ua = req.headers['user-agent'] || '';// Skip: non-GET, static files, loop guardif (req.method !== 'GET') return next();if (STATIC_EXT.test(req.path)) return next();if (req.headers['x-seorend-processed'] === '1') return next();if (!isBot(ua)) return next();const url = `${req.protocol}://${req.get('host')}${req.originalUrl}`;try {const response = await fetch(SEOREND_API_URL, {method: 'POST',headers: {'Content-Type': 'application/json','Authorization': `Bearer ${apiKey}`,},body: JSON.stringify({ url, userAgent: ua }),});if (!response.ok) return next();const data = await response.json();res.set('Content-Type', 'text/html; charset=utf-8');res.set('X-Seorend-Cache', data.cache?.hit ? 'HIT' : 'MISS');return res.status(data.status || 200).send(data.html);} catch {return next();}};}; -
Use in your Express app
Section titled “Use in your Express app”server.js const express = require('express');const seorend = require('./seorend.middleware');const app = express();// Add SeoRend BEFORE your static files and routesapp.use(seorend({ apiKey: process.env.SEOREND_API_KEY }));// Your existing middleware and routesapp.use(express.static('public'));app.get('*', (req, res) => res.sendFile('index.html', { root: 'public' }));app.listen(3000, () => console.log('Server running on port 3000'));
import Fastify from 'fastify';
const BOT_PATTERNS = [ 'googlebot', 'bingbot', 'yandexbot', 'facebookexternalhit', 'twitterbot', 'linkedinbot', 'gptbot', 'claudebot', 'perplexitybot',];
const STATIC_EXT = /\.(js|css|png|jpg|gif|ico|svg|webp|woff2?|ttf|pdf)$/i;
const fastify = Fastify();
fastify.addHook('onRequest', async (request, reply) => { const ua = request.headers['user-agent'] || ''; if (request.method !== 'GET') return; if (STATIC_EXT.test(request.url)) return; if (!BOT_PATTERNS.some(p => ua.toLowerCase().includes(p))) return;
const apiKey = process.env.SEOREND_API_KEY; const url = `https://${request.hostname}${request.url}`;
try { const res = await fetch('https://render.seorend.com/v1/render', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, }, body: JSON.stringify({ url, userAgent: ua }), });
if (!res.ok) return;
const data = await res.json(); reply.header('Content-Type', 'text/html; charset=utf-8'); reply.header('X-Seorend-Cache', data.cache?.hit ? 'HIT' : 'MISS'); reply.send(data.html); } catch { // Fallback — do nothing, request continues normally }});
// Your routes herefastify.listen({ port: 3000 });const http = require('http');const https = require('https');const fs = require('fs');const path = require('path');
const API_KEY = process.env.SEOREND_API_KEY;const BOT_PATTERNS = ['googlebot', 'bingbot', 'gptbot', 'claudebot', 'twitterbot'];const STATIC_EXT = /\.(js|css|png|jpg|gif|ico|svg|webp|woff2?|pdf)$/i;
function isBot(ua) { const lower = (ua || '').toLowerCase(); return BOT_PATTERNS.some(p => lower.includes(p));}
async function seorend(url, ua) { const body = JSON.stringify({ url, userAgent: ua }); return new Promise((resolve, reject) => { const req = https.request('https://render.seorend.com/v1/render', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${API_KEY}`, 'Content-Length': Buffer.byteLength(body), }, }, res => { let data = ''; res.on('data', c => data += c); res.on('end', () => resolve({ status: res.statusCode, data: JSON.parse(data) })); }); req.on('error', reject); req.write(body); req.end(); });}
const server = http.createServer(async (req, res) => { const ua = req.headers['user-agent'] || ''; const urlPath = req.url || '/';
if (req.method === 'GET' && !STATIC_EXT.test(urlPath) && isBot(ua)) { try { const url = `http://${req.headers.host}${urlPath}`; const { data } = await seorend(url, ua); res.writeHead(data.status || 200, { 'Content-Type': 'text/html; charset=utf-8' }); return res.end(data.html); } catch { /* fall through */ } }
// Serve static files const filePath = path.join(__dirname, 'public', urlPath === '/' ? 'index.html' : urlPath); fs.readFile(filePath, (err, content) => { if (err) { res.writeHead(404); res.end('Not found'); return; } res.writeHead(200); res.end(content); });});
server.listen(3000);Set Environment Variable
Section titled “Set Environment Variable”# Linux / macOSexport SEOREND_API_KEY=sk_live_your_key_here
# In a .env file (with dotenv)SEOREND_API_KEY=sk_live_your_key_here
# Run with env inlineSEOREND_API_KEY=sk_live_... node server.jsVerification
Section titled “Verification”# Should return X-Seorend-Cachecurl -s -I -A "Googlebot/2.1" http://localhost:3000/
# Should NOT have X-Seorend headerscurl -s -I -A "Mozilla/5.0" http://localhost:3000/Next Steps
Section titled “Next Steps”- Next.js integration — if you’re using Next.js App Router
- Testing guide — full testing checklist