/** * Post-build prerendering script. * Run after `vite build` to generate static HTML for each route. * Requires: npm install -D puppeteer serve * * Usage: node scripts/prerender.mjs */ import { readFileSync, writeFileSync, mkdirSync, readdirSync } from 'node:fs'; import { resolve, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const root = resolve(__dirname, '..'); const distDir = resolve(root, 'dist'); function getBlogRoutes() { try { const posts = JSON.parse(readFileSync(resolve(root, 'public/blog/posts.json'), 'utf-8')); return posts.map(p => `/blog/${p.slug}`); } catch { return []; } } function getPageRoutes() { try { const pagesDir = resolve(root, 'public/pages'); const files = readdirSync(pagesDir).filter(f => f.endsWith('.json')); return files .map(f => { const data = JSON.parse(readFileSync(resolve(pagesDir, f), 'utf-8')); return data.published ? `/p/${f.replace('.json', '')}` : null; }) .filter(Boolean); } catch { return []; } } const routes = [ '/', '/about', '/services', '/pricing', '/blog', '/privacy-policy', '/terms-of-use', ...getBlogRoutes(), ...getPageRoutes(), ]; async function prerender() { // Dynamic imports to avoid breaking build if puppeteer not installed const { default: puppeteer } = await import('puppeteer'); const { createServer } = await import('node:http'); const { createReadStream, existsSync } = await import('node:fs'); const { extname, join } = await import('node:path'); const { lookup } = await import('mime-types'); // Simple static file server for dist/ const PORT = 5199; const server = createServer((req, res) => { const urlPath = req.url.split('?')[0]; let filePath = join(distDir, urlPath === '/' ? '/index.html' : urlPath); // For SPA routes, serve index.html if (!existsSync(filePath) || !extname(filePath)) { filePath = join(distDir, 'index.html'); } const mime = lookup(filePath) || 'text/html'; res.setHeader('Content-Type', mime); createReadStream(filePath).pipe(res); }); await new Promise(r => server.listen(PORT, r)); console.log(`Static server running on http://localhost:${PORT}`); const browser = await puppeteer.launch({ args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu', ], }); for (const route of routes) { const url = `http://localhost:${PORT}${route}`; const page = await browser.newPage(); try { await page.goto(url, { waitUntil: 'networkidle0', timeout: 30000 }); // Wait for React to render await new Promise(r => setTimeout(r, 3000)); const html = await page.content(); const routeDir = route === '/' ? distDir : join(distDir, route); mkdirSync(routeDir, { recursive: true }); writeFileSync(join(routeDir, 'index.html'), html, 'utf-8'); console.log(`Prerendered: ${route}`); } catch (err) { console.error(`Failed to prerender ${route}:`, err.message); } finally { await page.close(); } } await browser.close(); server.close(); console.log('Prerendering complete!'); } prerender().catch(console.error);