[fix] deploy: 앱이 부르는 API 주소를 한 오리진으로 — :9800 기본값이 로그인을 막았다

nginx/site.conf 는 /v1 을 같은 오리진으로 프록시하고 주석에도 "앱과 같은 오리진이라
프리플라이트가 아예 발생하지 않는다" 고 적혀 있는데, compose 빌드 인자 기본값이
VITE_API_BASE_URL=http://localhost:9800 이었다. :80 으로 앱을 열면 번들이 :9800 을 부르므로
스스로 크로스 오리진이 되고, CLIENT_URL 기본값(3000~3005)에 http://localhost 가 없어
로그인만 실패한다.

증상이 사람을 속인다 — 서버는 200 에 토큰까지 내려보내고 브라우저가 allow-origin 이 없어
그 응답을 버리므로, 화면에는 "로그인에 실패했습니다" 만 뜬다. 비밀번호를 의심하게 된다.

- docker-compose.yml · .env.example: 기본값을 앱과 같은 오리진(http://localhost)으로.
  CORS 를 허용해 뚫는 게 아니라 크로스 오리진을 만들지 않는다
- .env.example: PUBLIC_API_BASE_URL 을 주석이 아니라 값으로 내놨다 — 주석으로 두면
  compose 기본값이 이기고, 그 기본값이 문제였다

검증: down -v 후 up -d --build → 번들의 localhost:9800 참조 0건 ·
POST http://localhost/v1/auth/login 200(프리플라이트 없음) · 프리렌더 재굽기 2건 ·
/ · /s/ · /s/<slug> 전부 200.
This commit is contained in:
hbyang 2026-09-07 16:19:34 +09:00
parent b665c34ac2
commit 55ee968f9d
3 changed files with 29 additions and 7 deletions

View File

@ -52,8 +52,14 @@ GOOGLE_CLIENT_ID=
# ── solution/frontend 브라우저가 부르는 주소 (compose 가 VITE_* 로 주입)
# ★ 브라우저가 부르는 주소다. 서버에 올리면 localhost 는 즉시 틀린다.
# PUBLIC_API_BASE_URL=http://localhost:9800
# PUBLIC_WEB_BASE_URL=http://localhost:3000
# ★ **앱과 같은 오리진을 적는다.** nginx(:80)가 /v1 을 같은 오리진으로 프록시하므로
# (nginx/site.conf) 앱이 부를 주소는 `:9800` 이 아니라 앱 주소 그 자체다. `:9800` 을 적으면
# 스스로 크로스 오리진을 만들어 CORS 가 붙고, 화면은 뜨는데 **로그인만 계속 실패한다** —
# 서버는 200 에 토큰까지 내려보내고 브라우저가 allow-origin 이 없어 그 응답을 버린다.
# 실측(2026-09-07): 이 기본값 그대로 띄우면 :80 으로 연 앱에서 로그인이 안 된다.
# ★ 값을 바꾸면 번들을 다시 구워야 한다: ./deploy.sh solution-site
PUBLIC_API_BASE_URL=http://localhost
PUBLIC_WEB_BASE_URL=http://localhost
# ── solution/site 발행물 — solution/backend 도 같이 본다
# canonical·og:url·sitemap·IndexNow 가 전부 SITE_PUBLIC_HOST 를 쓴다.

View File

@ -260,7 +260,9 @@ services:
args:
# ★ VITE_* 는 **번들에 구워진다.** .env 를 고쳐도 재빌드 전엔 안 바뀐다
# → 주소를 바꿨으면 `./deploy.sh solution-site`.
VITE_API_BASE_URL: ${PUBLIC_API_BASE_URL:-http://localhost:9800}
# ★ 기본값이 앱과 **같은 오리진**이다. nginx 가 /v1 을 프록시하므로 :9800 을 박으면
# 스스로 크로스 오리진을 만들어 로그인만 조용히 실패한다(위 주석 · .env.example).
VITE_API_BASE_URL: ${PUBLIC_API_BASE_URL:-http://localhost}
VITE_PUBLISH_HOST: ${SITE_PUBLIC_HOST:-localhost}
VITE_SITE_PREVIEW_URL: ${PUBLIC_WEB_BASE_URL:-http://localhost}
# ⚠️ 비어 있으면 자동 로그인은 아예 꺼진다(기본값 없음). 채우면 번들에 구워진다.

View File

@ -21,11 +21,25 @@ DB 를 안 보므로 **200 healthy** 로 뜨고, **워커만 조용히 재시작
같은 모양이 5개였다: `COLLECT_USE_PERPLEXITY`(값 `0` 이 `"0 # ..."` 가 된다) ·
`KAKAO_REST_API_KEY` · `TOUR_API_KEY` · `INDEXNOW_KEY` · `AZURE_STORAGE_CONNECTION_STRING`.
**고친 것** — `.env.example`: `DB_HOST` 기본값을 `host.docker.internal` 로 바꾸고 네이티브
실행 때만 127.0.0.1 이라는 것을 주석으로 남겼다. 값 뒤 주석은 전부 **윗줄로 올렸고**,
파일 머리에 "값 뒤에 주석을 붙이지 않는다" 를 근거와 함께 박았다.
**3) 앱이 스스로 크로스 오리진을 만든다.** `nginx/site.conf` 는 `/v1` 을 같은 오리진으로
프록시하고 주석에도 "앱과 같은 오리진이라 프리플라이트가 아예 발생하지 않는다" 고 적혀
있는데, compose 의 빌드 인자 기본값이 `VITE_API_BASE_URL=http://localhost:9800` 이었다.
`:80` 으로 앱을 열면 번들이 `:9800` 을 부르므로 크로스 오리진이 되고, `CLIENT_URL` 기본값
(3000~3005)에 `http://localhost` 가 없어 **로그인만 계속 실패한다.** 증상이 사람을 속인다 —
서버는 200 에 토큰까지 내려보내고, 브라우저가 `allow-origin` 이 없어 그 응답을 버리므로
화면에는 "로그인에 실패했습니다" 만 뜬다. 비밀번호를 의심하게 된다.
**검증** — 새 DB(`web4ai_db`)에 `init.sql` 적용 → `docker compose up -d` → 워커 정상 기동 →
**고친 것**
- `.env.example`: `DB_HOST` 기본값을 `host.docker.internal` 로. 값 뒤 주석은 전부 **윗줄로**
올리고, 파일 머리에 "값 뒤에 주석을 붙이지 않는다" 를 근거와 함께 박았다
- `.env.example` · `docker-compose.yml`: 앱이 부르는 API 주소 기본값을 **앱과 같은 오리진**
(`http://localhost`)으로. CORS 를 허용해서 뚫는 게 아니라 **크로스 오리진을 만들지 않는다** —
nginx 가 이미 같은 오리진으로 프록시하고 있었다. `PUBLIC_API_BASE_URL` 을 주석이 아니라
값으로 내놨다(주석으로 두면 compose 기본값이 이기고, 그 기본값이 문제였다)
**검증** — 새 DB(`web4ai_db`)에 `init.sql` 적용 → `docker compose down -v` 후 `up -d --build` →
번들에 `localhost:9800` 참조 0건 · `POST http://localhost/v1/auth/login` 200(프리플라이트 없음) ·
프리렌더가 기동하며 payload 2건 재굽기 → `/` `/s/` `/s/<slug>` 전부 200. 그리고 →
`scripts/demo_build.py` 로 발행: 게이트 통과 · `published: true` · 프리렌더가 굽고
`http://localhost/s/<slug>` 200. ★ 참고로 `demo_build.py` 는 자기 안에서 워커를 한 번 돌리는데,
compose 워커가 잡을 먼저 집어가므로 **스크립트 출력은 "게이트 거부"로 보인다** — 실제 결과는