Aimpress_site/scripts/prerender.mjs
Vadym Samoilenko 6cd63f1bdf Add Page Builder: 15 block types + publish/unpublish via TinaCMS
- New TinaCMS collection 'pages' with 15 block templates:
  Hero, TextBlock, TwoColumn, Features, Stats, Testimonials,
  Team, FAQ, CTABanner, Video, Gallery, Pricing, Timeline,
  Divider, ContactForm
- Each page has published toggle (unpublished → 404)
- Route /p/:slug renders dynamic pages from content/pages/*.json
- scripts/copy-pages.mjs copies content/pages → public/pages at build
- prerender.mjs extended to prerender published pages
- All blocks styled with design tokens + Framer Motion animations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 22:29:51 +00:00

107 lines
3.2 KiB
JavaScript

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