From 8bd168e1c7686616a66a203c0f25f35c2b4d84bb Mon Sep 17 00:00:00 2001 From: Haewon Kam Date: Mon, 14 Sep 2026 15:27:08 +0900 Subject: [PATCH] =?UTF-8?q?fix(worker):=20=EC=82=AC=EC=9D=B4=ED=8A=B8=20?= =?UTF-8?q?=EC=A3=BC=EC=86=8C=20=EC=84=A4=EC=A0=95=EC=9D=84=20build=20?= =?UTF-8?q?=EB=8B=A8=EA=B3=84=EC=97=90=EC=84=9C=EB=8F=84=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --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) --- workers/supporters-build/run.mjs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/workers/supporters-build/run.mjs b/workers/supporters-build/run.mjs index 1f8588e..cbccbf0 100644 --- a/workers/supporters-build/run.mjs +++ b/workers/supporters-build/run.mjs @@ -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/.txt 로 두면 빙·네이버가 소유를 확인한다. 검색엔진 소유 확인 메타 값은 briefs//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 });