new demo mode & tenure
Some checks failed
Build and publish Docker image / build-and-push (push) Successful in 8m43s
CI / Check (push) Failing after 8m49s

This commit is contained in:
Andras Schmelczer 2026-06-17 07:54:30 +01:00
parent 7656f24544
commit 4a0f00f2a4
64 changed files with 2875 additions and 338 deletions

View file

@ -12,9 +12,9 @@ const ROUTES = [
{
path: '/',
output: 'index.html',
title: 'Stop searching the wrong places | Perfect Postcode',
title: 'Find the best-value postcode in England | Perfect Postcode',
description:
'Filter every postcode in England by budget, commute, schools, crime, noise, broadband, property prices and amenities before you start chasing viewings.',
'Rank every postcode in England by value: £/sqm, sold prices, schools, commute, crime, noise and broadband. Find the underpriced postcode the market overlooked.',
},
{
path: '/learn',
@ -345,64 +345,64 @@ async function prerender() {
const MAX_ATTEMPTS = 3;
async function renderRoute(route) {
// A fresh context per attempt: pages otherwise share cache/storage, and a
// poisoned chunk-fetch in the shared cache makes a route fail every retry.
const context = await browser.createBrowserContext();
const page = await context.newPage();
// A fresh context per attempt: pages otherwise share cache/storage, and a
// poisoned chunk-fetch in the shared cache makes a route fail every retry.
const context = await browser.createBrowserContext();
const page = await context.newPage();
// Intercept API requests to prevent real fetches and retry loops.
await page.setRequestInterception(true);
page.on('request', (req) => {
const url = req.url();
if (url.includes('/api/features')) {
req.respond({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ groups: [] }),
});
} else if (url.includes('/api/poi-categories')) {
req.respond({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ groups: [] }),
});
} else if (url.includes('/api/pricing')) {
req.respond({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
licensed_count: 120,
current_price_pence: 999,
tiers: [
{ up_to: 50, price_pence: 99, slots: 50 },
{ up_to: 150, price_pence: 999, slots: 100 },
{ up_to: 250, price_pence: 2999, slots: 100 },
{ up_to: 350, price_pence: 4999, slots: 100 },
{ up_to: null, price_pence: 9999, slots: 0 },
],
}),
});
} else if (url.includes('/api/')) {
req.respond({
status: 200,
contentType: 'application/json',
body: '{}',
});
} else {
req.continue();
}
// Intercept API requests to prevent real fetches and retry loops.
await page.setRequestInterception(true);
page.on('request', (req) => {
const url = req.url();
if (url.includes('/api/features')) {
req.respond({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ groups: [] }),
});
} else if (url.includes('/api/poi-categories')) {
req.respond({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ groups: [] }),
});
} else if (url.includes('/api/pricing')) {
req.respond({
status: 200,
contentType: 'application/json',
body: JSON.stringify({
licensed_count: 120,
current_price_pence: 999,
tiers: [
{ up_to: 50, price_pence: 99, slots: 50 },
{ up_to: 150, price_pence: 999, slots: 100 },
{ up_to: 250, price_pence: 2999, slots: 100 },
{ up_to: 350, price_pence: 4999, slots: 100 },
{ up_to: null, price_pence: 9999, slots: 0 },
],
}),
});
} else if (url.includes('/api/')) {
req.respond({
status: 200,
contentType: 'application/json',
body: '{}',
});
} else {
req.continue();
}
});
try {
await page.goto(`http://127.0.0.1:${port}${route.path}`, {
waitUntil: 'networkidle0',
timeout: 30000,
});
try {
await page.goto(`http://127.0.0.1:${port}${route.path}`, {
waitUntil: 'networkidle0',
timeout: 30000,
});
await page.waitForSelector('h1', { timeout: 10000 });
await page.waitForSelector('h1', { timeout: 10000 });
// Extract and clean the rendered HTML.
const html = await page.evaluate(() => {
// Extract and clean the rendered HTML.
const html = await page.evaluate(() => {
const root = document.getElementById('root');
if (!root) return '';
@ -420,18 +420,18 @@ async function prerender() {
});
return root.innerHTML;
});
});
if (!html || html.length < MIN_HTML_CHARS) {
throw new Error(
`Prerender produced too little HTML for ${route.path} (${html?.length ?? 0} chars)`
);
}
return html;
} finally {
await context.close().catch(() => {});
if (!html || html.length < MIN_HTML_CHARS) {
throw new Error(
`Prerender produced too little HTML for ${route.path} (${html?.length ?? 0} chars)`
);
}
return html;
} finally {
await context.close().catch(() => {});
}
}
try {