Cloudflare 배포 (frontend)
frontend/ (Next.js 16, App Router) 를 Cloudflare Workers + Static Assets 로 배포한다. Vercel 에서 이전 (2026-05-09 결정, 2026-05-10 셋업).
어댑터: @opennextjs/cloudflare
- Next.js 16+ 공식 CF 어댑터.
- 구
@cloudflare/next-on-pages는 Next 15.5.2 이하만 지원 → 사용 X. - deploy 타겟은 Workers + Static Assets (Pages 가 아님). Pages 로 deploy 시
worker.js가 실행 안 돼서 SSR/ISR 페이지 모두 404.
빌드 / 배포 명령
cd frontend
npm run build:cf # .open-next/ (worker.js + assets/) 산출
npm run preview:cf # wrangler 로 로컬 프리뷰
npm run deploy:cf # wrangler 로 CF Workers 에 업로드 (인증 필요)설정 파일
open-next.config.ts— 어댑터 설정 (현재 기본값)wrangler.toml— Workers 프로젝트 설정name = "dartbrief"— dashboard Project name 과 일치 필수 (불일치 시 빌드 에러 또는 다른 Worker 에 배포)main = ".open-next/worker.js"— SSR/ISR 진입점compatibility_flags = ["nodejs_compat"]— OpenNext 어댑터 필수 (Node 빌트인 폴리필)[assets] binding="ASSETS" directory=".open-next/assets"— 정적 자산 binding (Worker 가 매칭 안 되는 경로는 자산으로 서빙)
CF Dashboard 셋업
Workers & Pages → Create → Workers → Continue with GitHub:
| 필드 | 값 |
|---|---|
| Project name | dartbrief |
| Repository | junhyeon47/dartbrief |
| Production branch | main |
| Root directory | frontend ⚠️ (monorepo, 누락 시 빌드 실패) |
| Build command | npm run build:cf |
| Deploy command | npx wrangler deploy |
⚠️ CF Pages 로 셋업하면 안 됨 — worker.js 미실행으로 SSR/ISR 404.
환경 변수 (CF dashboard)
| 키 | 용도 | 예시 | Type |
|---|---|---|---|
API_BASE_URL | server-side fetch (ISR) 의 백엔드 base | https://api.dartbrief.com | Plaintext |
WEB_SECRET | backend AppCheck 의 대안 ACL — X-Worker-Secret 헤더로 전송. 모바일은 AppCheck, 웹 Worker 는 이 secret 으로 인증 | (랜덤 32+자 문자열) | Encrypted |
NEXT_PUBLIC_SITE_PASSWORD | SiteGate 비공개 게이트 (런치 전까지) | (시크릿) | Plaintext (빌드 시점 주입) |
NEXT_PUBLIC_APPLE_APP_ID | Smart App Banner App Store ID | App Store 등록 후 | Plaintext |
NEXT_PUBLIC_* 만 클라이언트 노출. 그 외는 빌드/SSR 시점에만 사용.
등록 방법 — 변수 경로 3가지
CF Workers 는 빌드 시점 vs 런타임 경로가 분리되어 있다. 잘못 골라 등록하면 process.env 가 undefined 로 번들링되거나 런타임에서 안 보인다.
| 경로 | 가시성 | 적합 | 부적합 |
|---|---|---|---|
Settings → Build → Build Variables and Secrets (또는 로컬 .env.production / shell export) | next build 시점에 process.env 로 노출 → client bundle 에 inline | NEXT_PUBLIC_* (예: NEXT_PUBLIC_SITE_PASSWORD, NEXT_PUBLIC_APPLE_APP_ID) | 런타임 시크릿 (빌드 산출물에 박혀버림) |
wrangler.toml [vars] 또는 dashboard Variables (Plaintext) | Worker 런타임에 env 바인딩 | server-side fetch base URL (예: API_BASE_URL) | NEXT_PUBLIC_* (빌드 시점엔 안 보여 undefined 됨) |
wrangler secret put (Encrypted) | Worker 런타임에 env 바인딩, dashboard 에서 값 비공개 | 진짜 런타임 시크릿 (예: WEB_SECRET — backend 호출 시 SSR 헤더용) | 빌드 시점에 필요한 값 |
NEXT_PUBLIC_SITE_PASSWORD 는 어차피 client bundle 에 inline 되어 노출되므로 secret 보호 의미가 없다. Build Variables 경로로 등록.
cd frontend
# 진짜 런타임 시크릿 — Railway 의 WEB_SECRET 과 동일한 값 입력
npx wrangler secret put WEB_SECRET --name dartbrief
# 등록 확인 (값은 안 보임)
npx wrangler secret list --name dartbrief⚠️ WEB_SECRET 누락 시 prod 홈/피드가 통째로 빈 목록으로 보임. 백엔드 App Check 가 SSR fetch 를 403 으로 차단하고, frontend getJSONSoft 가 throw 를 삼켜 "공시 목록을 불러올 수 없습니다." 만 렌더링. dashboard 에서 secret 표시 안 되더라도 wrangler secret list 로 검증할 것 (PR #67 머지 후 secret 등록 누락 사고 — 2026-05-10).
ISR / 라우트
| 경로 | 모드 | revalidate |
|---|---|---|
/, /privacy, /terms, /support | static | — |
/disclosures | ISR | 60s |
/disclosures/[rceptNo] | dynamic + revalidate | 300s |
/companies/[corpCode] | dynamic + revalidate | 300s |
/api/stocks/search | route handler | no-cache (백엔드가 10분 캐시) |
서버 fetch 는 모두 백엔드 public 엔드포인트 (lazy auth, anonymous):
GET /api/v1/disclosures/recentGET /api/v1/disclosures/{rceptNo}GET /api/v1/disclosures?corpCode=X&limit=50(limit 기본 50, 상한 200)GET /api/v1/companies/{corpCode}GET /api/v1/ai/analyze/{rceptNo}(anonymous →resultomit,summary만)
분석 본문(reveal) 은 quota 정책상 앱에서만. 웹은 한 줄 요약 + "앱에서 보기" CTA.
App Check
현재 strict/soft 모드 — 백엔드가 web origin 의 fetch 를 차단할 수 있음. 차단 시 별도 PR be-web-appcheck-exempt 로 origin whitelist 처리 (lazy).
비용
- CF Workers 무료: 10만 요청/일 (≈300만/월)
- 정적 자산: 무제한 무료 (CDN edge)
- ISR 캐시 hit → Worker 호출 X, CDN 만 → 무료
- Worker 호출은 캐시 미스 + API route + revalidate 만
- MAU 5만 정도까진 무료 티어로 충분
흔한 함정
pages_build_output_dir셋업 → 404: OpenNext 의 worker.js 가 실행 안 됨. 반드시 Workers +[assets]binding 으로.nodejs_compat누락 → 런타임 500: OpenNext 어댑터가 Node 빌트인 폴리필 사용. wrangler.toml 의compatibility_flags또는 dashboard 에 명시.esbuild/wrangler미설치 → 빌드 실패:@opennextjs/cloudflare의 peer dep.package.jsondevDependency 에 명시 (PR #65 fix).- Root directory 누락 → 빌드 실패: monorepo 라 dashboard 에
frontend명시 필수. WEB_SECRET미등록 → 홈/피드 통째 빈 목록: secret 은wrangler.toml에 안 들어가므로wrangler secret put으로 별도 등록해야 함. 누락 시 SSR fetch 가 백엔드에서 403,getJSONSoft가 swallow → "공시 목록을 불러올 수 없습니다." (런타임 에러로 안 떠서 발견 늦음). 배포 후npx wrangler secret list --name dartbrief로 검증 필수.