/** * Пререндер лендинга в статический HTML (SEO). * * Работает БЕЗ браузера: react-dom/server рендерит в Node, готовый * DOM вставляется между маркерами в 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 = ""; const 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(); if (!snapshot.includes(" — похоже, отрендерилась не главная"); } 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); }