portfolio-page/prerender.js

114 lines
3.1 KiB
JavaScript

/**
* 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 'node:http'
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import puppeteer from 'puppeteer'
const __dirname = dirname(fileURLToPath(import.meta.url))
const DIST = join(__dirname, 'dist')
const PORT = 4173
const ROUTES = [
'/de/',
'/de/technical',
'/en/',
'/en/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/<route>/index.html
// Normalise the route: strip trailing slash for directory creation (except root, which is now redirected)
const routeDir =
route.endsWith('/') && route !== '/' ? route.slice(0, -1) : route
const outDir = routeDir === '/' ? DIST : join(DIST, routeDir)
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')
}
try {
await prerender()
} catch (err) {
console.error('Prerendering failed:', err)
process.exit(1)
}