diff --git a/solution/site/scripts/prerender.ts b/solution/site/scripts/prerender.ts index 0513cbc..299c843 100644 --- a/solution/site/scripts/prerender.ts +++ b/solution/site/scripts/prerender.ts @@ -1056,21 +1056,31 @@ function writePreviewShell(outRoot: string, assets: {script: string; css: string /** * 이미 구워져 있는 사이트에서 오리진을 알아낸다. 한 장도 없으면 빈 문자열. * - * ★ payload 를 읽지 않는 경로(`--refresh-assets` 기동)에서 루트 색인 파일을 다시 쓰려면 + * ★ payload 를 읽지 않는 경로(`--seed-assets` 기동)에서 루트 색인 파일을 다시 쓰려면 * 오리진이 필요한데, 그 값은 payload 에만 있다. 디스크에 있는 페이지가 자기 canonical 로 * 선언해 둔 것을 쓴다(readBakedOrigin 주석). + * ★ **가장 최근에 구워진 페이지**의 오리진을 쓴다 — 처음 만난 것을 쓰면(readdirSync 순서는 + * 보장이 없다) 호스트를 옮긴 뒤 한 번도 재발행 안 한 옛 사이트(목업 포함) 하나가 걸릴 때 + * 전체 sitemap.xml 이 죽은 옛 호스트로 통째로 구워진다. 실측(2026-09-18): SITE_PUBLIC_HOST + * 를 web4ai.o2osolution.ai 로 옮긴 지 오래인데, solution-worker 가 재시작(배포)될 때마다 + * 이 함수가 옛 호스트(w4ai.o2o.kr)로 구운 사이트 하나를 집어 사이트맵 전체가 죽은 주소로 + * 덮였다 — 오늘 새로 발행한 사이트까지 사이트맵에서 옛 호스트로 나갔다. 갓 구운 페이지일수록 + * 지금 SITE_PUBLIC_HOST 를 반영했을 확률이 높다. */ function findBakedOrigin(outRoot: string): string { const sitesDir = join(outRoot, SITE_DIR); if (!existsSync(sitesDir)) return ''; + let newest: {origin: string; mtimeMs: number} | null = null; for (const entry of readdirSync(sitesDir, {withFileTypes: true})) { if (!entry.isDirectory()) continue; const file = join(sitesDir, entry.name, 'index.html'); if (!existsSync(file)) continue; const origin = readBakedOrigin(readFileSync(file, 'utf-8')); - if (origin) return origin; + if (!origin) continue; + const mtimeMs = statSync(file).mtimeMs; + if (!newest || mtimeMs > newest.mtimeMs) newest = {origin, mtimeMs}; } - return ''; + return newest?.origin ?? ''; } function writeRootMachineFiles(outRoot: string, origin: string) {