o2o-infinith-demo/scripts/prerender.ts
Haewon Kam 13f8c362c2 feat: 랜딩 AI 크롤러 가독성 — SSR 프리렌더 + 메타/JSON-LD + robots/sitemap/llms.txt
- src/entry-server.tsx + scripts/prerender.ts: 빌드 시 / 를 renderToString으로 정적 HTML 주입 (크롤러 본문 4단어 → 800+단어)
- main.tsx: 프리렌더된 랜딩은 hydrateRoot, 나머지 라우트는 createRoot
- index.html: lang=ko, description/OG/canonical, Organization·SoftwareApplication·WebSite JSON-LD
- public/: robots.txt(AI 크롤러 명시 허용), sitemap.xml, llms.txt (SPA 리라이트로 index.html 반환되던 문제 해소)
- Badge.tsx: 메인 체크아웃에만 있고 미커밋이던 컴포넌트 추가 (빌드 실패 원인)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-28 15:43:47 +09:00

20 lines
1003 B
TypeScript

// vite build(클라이언트) + vite build --ssr(서버) 이후 실행.
// dist/server/entry-server.js 의 render()로 랜딩 HTML을 만들어 dist/index.html 의 #root 에 주입한다.
import { readFileSync, writeFileSync, rmSync } from 'node:fs';
import { resolve } from 'node:path';
const dist = resolve(process.cwd(), 'dist');
const template = readFileSync(resolve(dist, 'index.html'), 'utf-8');
const { render } = await import(resolve(dist, 'server/entry-server.js'));
const routes = ['/'];
for (const url of routes) {
const appHtml = render(url);
const html = template.replace('<div id="root"></div>', `<div id="root">${appHtml}</div>`);
const out = url === '/' ? 'index.html' : `${url.replace(/^\//, '')}/index.html`;
writeFileSync(resolve(dist, out), html);
const words = appHtml.replace(/<[^>]*>/g, ' ').split(/\s+/).filter(Boolean).length;
console.log(`[prerender] ${url} -> dist/${out} (${words} words)`);
}
rmSync(resolve(dist, 'server'), { recursive: true, force: true });