fix(worker): 사이트 주소 설정을 build 단계에서도 적용

--from build 로 재배포하면 vercel.json 의 SITE_URL 이 갱신되지 않아 canonical 과
사이트맵이 옛 .vercel.app 주소로 나갔다. 그 코드가 data 단계에만 있었기 때문이다.
오늘 세 사이트를 새 도메인에 붙이고 확인하다 드러났다.

applySiteUrl() 로 떼어내 data 와 build 두 단계에서 부른다. 어느 지점부터 돌려도
주소가 반영된다. 색인 허용 여부(PUBLIC_INDEXABLE, X-Robots-Tag 제거)도 같은 함수가 맡는다.

검증: 세 사이트 재배포 후 canonical·사이트맵이 모두 새 주소로 바뀌고 noindex 는 유지된다.
- viewpl.webforai.kr · wonjin.webforai.kr · oracle.webforai.kr

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Haewon Kam 2026-09-14 15:27:08 +09:00
parent 98d52f20f1
commit 8bd168e1c7

View File

@ -40,6 +40,21 @@ const startFrom = opt('from', 'evidence');
(function loadEnv() { const p = join(ROOT, '.env'); if (!existsSync(p)) return; for (const line of readFileSync(p, 'utf8').split('\n')) { const m = line.match(/^\s*([A-Z0-9_]+)\s*=\s*(.*)\s*$/); if (m && !process.env[m[1]]) process.env[m[1]] = m[2].replace(/^["']|["']$/g, ''); } })();
// 사이트 주소와 색인 허용 여부를 vercel.json 에 쓴다. 원격 빌드가 이 값으로 canonical·사이트맵·robots 를 만든다.
// data 단계와 build 단계에서 모두 부른다. --from build 처럼 중간부터 돌려도 주소가 반영되게 하기 위해서다.
// 예전에는 data 단계에만 있어서 --from build 로 재배포하면 옛 주소가 canonical 로 그대로 나갔다(2026-09-14).
function applySiteUrl() {
const liveUrl = opt('domain') ? `https://${opt('domain').replace(/^https?:\/\//, '').replace(/\/$/, '')}` : `https://supporters-${clinic}.vercel.app`;
const vp = join(SITE, 'vercel.json');
if (!existsSync(vp)) return liveUrl;
const vj = readJson(vp);
vj.build = vj.build ?? {};
vj.build.env = { ...(vj.build.env ?? {}), SITE_URL: liveUrl, PUBLIC_INDEXABLE: flag('indexable') ? 'true' : 'false' };
if (flag('indexable')) vj.headers = (vj.headers ?? []).filter((h) => !(h.headers ?? []).some((x) => x.key === 'X-Robots-Tag'));
writeJson(vp, vj);
return liveUrl;
}
// ---------- 상태 ----------
const statusPath = join(WORK, 'status.json');
const status = existsSync(statusPath) ? JSON.parse(readFileSync(statusPath, 'utf8')) : { clinic, url, status: 'queued', phases: [], posts: [], createdAt: new Date().toISOString() };
@ -121,10 +136,7 @@ await phase('data', () => {
if (existsSync(join(KEEP, 'posts'))) cpSync(join(KEEP, 'posts'), POSTS, { recursive: true });
if (existsSync(join(KEEP, 'videos.json'))) cpSync(join(KEEP, 'videos.json'), join(DATA, 'videos.json'));
for (const rel of ['astro.config.mjs', 'vercel.json']) { const p = join(SITE, rel); writeFileSync(p, readFileSync(p, 'utf8').split('__CLINIC_ID__').join(clinic)); }
// 색인 허용 빌드(--indexable): noindex 헤더 제거 + PUBLIC_INDEXABLE=true(승인된 글만 발행). 정식 도메인(--domain)이 있으면 SITE_URL 을 그 주소로.
const liveUrl = opt('domain') ? `https://${opt('domain').replace(/^https?:\/\//, '').replace(/\/$/, '')}` : `https://supporters-${clinic}.vercel.app`;
{ const vp = join(SITE, 'vercel.json'); const vj = readJson(vp); vj.build = vj.build ?? {}; vj.build.env = { ...(vj.build.env ?? {}), SITE_URL: liveUrl, PUBLIC_INDEXABLE: flag('indexable') ? 'true' : 'false' };
if (flag('indexable')) vj.headers = (vj.headers ?? []).filter((h) => !(h.headers ?? []).some((x) => x.key === 'X-Robots-Tag')); writeJson(vp, vj); }
applySiteUrl();
// IndexNow 키: 사이트마다 하나. public/<key>.txt 로 두면 빙·네이버가 소유를 확인한다. 검색엔진 소유 확인 메타 값은 briefs/<clinic>/clinic.json 의 site 로 들어온다(inputs 단계).
{ const sp = join(DATA, 'site.json'); const sj = readJson(sp); if (!sj.indexNowKey) { sj.indexNowKey = [...Array(32)].map(() => Math.floor(Math.random() * 16).toString(16)).join(''); writeJson(sp, sj); }
mkdirSync(join(SITE, 'public'), { recursive: true }); writeFileSync(join(SITE, 'public', `${sj.indexNowKey}.txt`), sj.indexNowKey); }
@ -442,6 +454,8 @@ await phase('heroes', () => {
});
await phase('build', () => {
// 주소·색인 설정을 먼저 적용한다. 원격 빌드가 이 값으로 canonical·사이트맵을 만든다.
applySiteUrl();
const inst = run('npm', ['ci', '--no-audit', '--no-fund'], { cwd: SITE });
if (!inst.ok) throw new Error(`npm ci 실패\n${tail(inst.out)}`);
const b = run('npx', ['astro', 'build'], { cwd: SITE });