/** * 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); });