Files
neon-bar86.ru/site/scripts/prerender.tsx
T
2026-10-11 22:24:54 +05:00

70 lines
3.0 KiB
TypeScript

/**
* Пререндер лендинга в статический HTML (SEO).
*
* Работает БЕЗ браузера: react-dom/server рендерит <App /> в Node, готовый
* DOM вставляется между маркерами <!--SEO:FALLBACK_*--> в dist/index.html.
* useEffect в SSR не исполняется, поэтому вызовов /api/* в снапшоте нет —
* страница полностью статична; стоп-лист подмешивается уже на клиенте.
*
* Снапшот заменяется React (createRoot) при монтировании — SPA работает
* как раньше. Повторные запуски идемпотентны (маркеры сохраняются).
*
* Ошибка рендера не ломает сборку: остаётся статический fallback-блок
* (H1 + SEO-абзац), в консоль пишется предупреждение.
*
* Запуск: npm run build (vite build && tsx scripts/prerender.tsx)
*/
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import * as React from "react";
import { renderToStaticMarkup } from "react-dom/server";
import App from "../src/App";
const here = dirname(fileURLToPath(import.meta.url));
const distIndex = join(here, "..", "dist", "index.html");
// флаг для src/lib/prerender.ts (секции рендерятся «видимыми») — до рендера
(globalThis as { __NEON_PRERENDER__?: boolean }).__NEON_PRERENDER__ = true;
// App читает document.title в useRef — минимальный полифилл для Node
if (typeof (globalThis as { document?: unknown }).document === "undefined") {
(globalThis as { document?: unknown }).document = { title: "" };
}
const START = "<!--SEO:FALLBACK_START-->";
const END = "<!--SEO:FALLBACK_END-->";
if (!existsSync(distIndex)) {
console.error("[prerender] dist/index.html не найден — сначала vite build");
process.exit(1);
}
const html = readFileSync(distIndex, "utf-8");
if (!html.includes(START) || !html.includes(END)) {
console.warn("[prerender] маркеры SEO:FALLBACK_* не найдены — снапшот не вставлен");
process.exit(0);
}
try {
const snapshot = renderToStaticMarkup(<App />);
if (!snapshot.includes("<h1")) {
throw new Error("в снапшоте нет <h1> — похоже, отрендерилась не главная");
}
const updated = html.replace(
new RegExp(`${START}[\\s\\S]*?${END}`),
`${START}${snapshot}${END}`,
);
writeFileSync(distIndex, updated, "utf-8");
console.log(
`[prerender] снапшот вставлен: ${Math.round(snapshot.length / 1024)} KB внутрь #root`,
);
} catch (error) {
console.warn(
"[prerender] рендер не удался, в dist остался статический fallback:",
error instanceof Error ? error.message : error,
);
process.exit(0);
}