first commit
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
// 빌드 후 실행: 라우트별 완성 HTML(<head> + 본문)을 dist 에 생성하는 사전 렌더링(SSG) 스크립트
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath, pathToFileURL } from 'url';
|
||||
import { DEFAULT_PATH } from '../src/config.js';
|
||||
import { getPrerenderPaths } from './routes.js';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const root = path.resolve(__dirname, '..');
|
||||
const distDir = path.join(root, 'dist');
|
||||
const ssrEntry = path.join(root, 'dist-ssr', 'entry-server.js');
|
||||
|
||||
const HEAD_PLACEHOLDER = '<!--app-head-->';
|
||||
const HTML_PLACEHOLDER = '<!--app-html-->';
|
||||
|
||||
const template = fs.readFileSync(path.join(distDir, 'index.html'), 'utf-8');
|
||||
if (!template.includes(HEAD_PLACEHOLDER) || !template.includes(HTML_PLACEHOLDER)) {
|
||||
throw new Error('dist/index.html 에서 <!--app-head--> / <!--app-html--> 자리표시자를 찾지 못했습니다.');
|
||||
}
|
||||
// Cloudflare Workers (_worker.js) 생성용으로 원본 템플릿 보존
|
||||
fs.writeFileSync(path.join(distDir, '_template.html'), template, 'utf-8');
|
||||
|
||||
const { render } = await import(pathToFileURL(ssrEntry).href);
|
||||
|
||||
const writeFile = (file, content) => {
|
||||
fs.mkdirSync(path.dirname(file), { recursive: true });
|
||||
fs.writeFileSync(file, content, 'utf-8');
|
||||
};
|
||||
|
||||
// url -> 출력 파일 경로
|
||||
const popularPaths = [
|
||||
DEFAULT_PATH,
|
||||
'/incheon/yeonsu-gu/songdo-dong/middle/math',
|
||||
'/daegu/suseong-gu/hwanggeum-dong/middle/math',
|
||||
'/daegu/suseong-gu/hwanggeum-dong/high/korean',
|
||||
'/seoul/gangnam-gu/daechi-dong/high/math',
|
||||
'/gyeonggi/seongnam-si-bundang-gu/sunae-dong/middle/english',
|
||||
'/busan/haeundae-gu/u-dong/middle/english'
|
||||
];
|
||||
const allLandingPaths = Array.from(new Set([...popularPaths, ...getPrerenderPaths()]));
|
||||
|
||||
const targets = [
|
||||
{ url: '/', file: path.join(distDir, 'index.html'), kind: 'home' }, // 메인 홈 화면 (지역·학년·과목 허브)
|
||||
{ url: '/regions', file: path.join(distDir, 'regions', 'index.html'), kind: 'page' },
|
||||
{ url: '/admin', file: path.join(distDir, 'admin', 'index.html'), kind: 'noindex' },
|
||||
{ url: '/__not-found__', file: path.join(distDir, '404.html'), kind: 'noindex' },
|
||||
...allLandingPaths.map((p) => ({
|
||||
url: p,
|
||||
file: path.join(distDir, ...p.split('/').filter(Boolean), 'index.html'),
|
||||
kind: 'landing'
|
||||
}))
|
||||
];
|
||||
|
||||
const problems = [];
|
||||
let count = 0;
|
||||
|
||||
for (const t of targets) {
|
||||
const { html, head, routeName } = await render(t.url);
|
||||
|
||||
let out = template;
|
||||
if (head) {
|
||||
out = out.replace(/<title\s+data-default[\s\S]*?<\/title>\s*/i, '');
|
||||
out = out.replace(/<[^>]+data-default[^>]*>\s*/gi, '');
|
||||
out = out.replace(HEAD_PLACEHOLDER, head);
|
||||
} else {
|
||||
out = out.replace(HEAD_PLACEHOLDER, '');
|
||||
}
|
||||
out = out.replace(HTML_PLACEHOLDER, html);
|
||||
|
||||
// 품질 검증: 색인 대상 페이지는 h1 1개, title, canonical, JSON-LD 가 반드시 있어야 함
|
||||
if (t.kind === 'landing' || t.kind === 'home' || t.kind === 'page') {
|
||||
const h1 = (html.match(/<h1[\s>]/g) || []).length;
|
||||
if (h1 !== 1) problems.push(`${t.url}: h1 ${h1}개`);
|
||||
if (!/<title data-head>[^<]+<\/title>/.test(head)) problems.push(`${t.url}: title 없음`);
|
||||
if (!/rel="canonical"/.test(head)) problems.push(`${t.url}: canonical 없음`);
|
||||
if (!/application\/ld\+json/.test(head) && t.kind !== 'page') problems.push(`${t.url}: JSON-LD 없음`);
|
||||
}
|
||||
if (t.kind === 'landing' && routeName !== 'LocationSubjectFullLanding') {
|
||||
problems.push(`${t.url}: 랜딩 라우트에 매칭되지 않음 (${String(routeName)})`);
|
||||
}
|
||||
|
||||
writeFile(t.file, out);
|
||||
count++;
|
||||
}
|
||||
|
||||
if (problems.length > 0) {
|
||||
console.error('[FAIL] 사전 렌더링 검증 실패:\n - ' + problems.join('\n - '));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// SSR 번들은 배포 대상이 아니므로 정리하지 않고 dist-ssr 에 남겨둠 (.gitignore 대상)
|
||||
console.log(`[OK] ${count}개 페이지 사전 렌더링 완료 -> dist/`);
|
||||
Reference in New Issue
Block a user