/** * Post-build prerendering script. * Starts a local server from dist/, visits each route with Puppeteer, * and saves the fully-rendered HTML so crawlers see real content. * * Usage: node prerender.js (run after `vite build`) */ import { createServer } from 'http' import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs' import { join, dirname } from 'path' import { fileURLToPath } from 'url' import puppeteer from 'puppeteer' const __dirname = dirname(fileURLToPath(import.meta.url)) const DIST = join(__dirname, 'dist') const PORT = 4173 const ROUTES = ['/', '/technical', '/imprint', '/privacy-policy'] /** Minimal static file server that falls back to index.html (SPA). */ function startServer() { const mimeTypes = { '.html': 'text/html', '.js': 'application/javascript', '.css': 'text/css', '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', '.svg': 'image/svg+xml', '.pdf': 'application/pdf', '.woff2': 'font/woff2', '.woff': 'font/woff', } const server = createServer((req, res) => { let filePath = join(DIST, req.url === '/' ? '/index.html' : req.url) if (!existsSync(filePath) || !filePath.includes('.')) { filePath = join(DIST, 'index.html') } const ext = '.' + filePath.split('.').pop() const contentType = mimeTypes[ext] || 'application/octet-stream' try { const content = readFileSync(filePath) res.writeHead(200, { 'Content-Type': contentType }) res.end(content) } catch { res.writeHead(404) res.end('Not found') } }) return new Promise((resolve) => { server.listen(PORT, () => { console.log(` Server running on http://localhost:${PORT}`) resolve(server) }) }) } async function prerender() { console.log('\nšŸ” Prerendering routes...\n') const server = await startServer() const browser = await puppeteer.launch({ headless: true }) for (const route of ROUTES) { const page = await browser.newPage() const url = `http://localhost:${PORT}${route}` console.log(` Rendering ${route} ...`) await page.goto(url, { waitUntil: 'networkidle0', timeout: 15000 }) // Wait a bit for React to finish any async rendering await new Promise((r) => setTimeout(r, 1500)) const html = await page.content() await page.close() // Write to dist//index.html const outDir = route === '/' ? DIST : join(DIST, route) if (!existsSync(outDir)) { mkdirSync(outDir, { recursive: true }) } const outFile = join(outDir, 'index.html') writeFileSync(outFile, html, 'utf-8') console.log(` āœ“ Saved ${outFile.replace(DIST, 'dist')}`) } await browser.close() server.close() console.log('\nāœ… Prerendering complete!\n') } prerender().catch((err) => { console.error('Prerendering failed:', err) process.exit(1) })