"Vercel에서는 되는데 운영에서 안 되는 이유"
"로컬에서는 잘 되는데 왜 배포하면 안 될까요?"
11년차 QA 엔지니어로서 수없이 들어본 질문이었습니다. 그때는 단순히 "환경 차이" 정도로만 이해했지만, 직접 개발을 하면서 그 "환경 차이"의 실체를 마주하게 되었습니다. 특히 CORS(Cross-Origin Resource Sharing)와 도메인 정책은 정말... 악몽 그 자체였습니다.
처음 FastAPI로 백엔드를 만들고, React로 프론트엔드를 만들어 연결하려는데 브라우저 콘솔에 빨간 에러가 떴습니다.
Access to XMLHttpRequest at 'http://localhost:8000/api/users' from origin 'http://localhost:3000' has been blocked by CORS policy
"아니, 같은 localhost인데 왜 안 되는 거야?"
알고 보니 포트가 다르면 브라우저는 다른 출처(Origin)로 인식한다는 것이었습니다.
http://localhost:3000과 http://localhost:8000은 엄연히 다른 도메인이었던 것이죠.
처음엔 이렇게 간단하게 해결될 줄 알았습니다.
로컬에서는 잘 돌아갔습니다. "와, 생각보다 쉽네?" 라고 생각한 순간이 함정의 시작이었습니다.
Vercel에 배포하니 상황이 복잡해졌습니다. Vercel은 브랜치별로 동적 URL을 생성합니다.
https://jamescompany-git-develop-james.vercel.app
https://jamescompany-abc123-james.vercel.app (PR 프리뷰)
https://api-jamescompany-git-develop-james.vercel.app
하지만 FastAPI의 CORSMiddleware는 와일드카드를 지원하지 않았습니다!
결국 커스텀 미들웨어를 만들어야 했습니다.
운영 환경에서는 모든 것이 HTTPS여야 했습니다. Cloudflare를 통해 SSL을 적용했지만, 새로운 문제들이 나타났습니다.
프론트엔드는 HTTPS인데 백엔드 API를 HTTP로 호출하면 브라우저가 차단합니다.
Cloudflare에서 HSTS를 활성화하니 더 엄격해졌습니다.
인증 시스템을 구현하면서 쿠키 설정도 환경별로 달라야 했습니다.
개발 환경에서 SameSite=None을 사용하면 반드시 Secure=True여야 합니다. 하지만 로컬은 HTTP라서 Secure=True를 쓸 수 없습니다. 환경별로 다르게 설정해야 하는 이유였습니다.
운영 환경에서 쿠키 도메인을 .jamescompany.kr로 설정하면,
www.jamescompany.kr
api.jamescompany.kr
admin.jamescompany.kr
모든 서브도메인에서 쿠키를 공유할 수 있습니다.
CORS 정책 때문에 브라우저는 실제 요청 전에 OPTIONS 메서드로 "이 요청 보내도 되나요?"라고 먼저 물어봅니다. 이게 Preflight 요청입니다.
GET, POST가 아닌 메서드 사용
Content-Type이 application/json인 경우
커스텀 헤더가 있는 경우
원인: withCredentials: true 설정 누락
해결: axios 인스턴스에 기본값으로 설정
원인: 동적 URL 때문에 와일드카드 필요
해결: 커스텀 CORS 미들웨어 구현
원인: SameSite=Strict인데 도메인이 달라서
해결: API를 같은 도메인의 서브도메인으로 변경 (api.jamescompany.kr)
해결: mkcert를 사용한 로컬 HTTPS 설정
CORS와 도메인 정책은 단순히 "설정 몇 줄 추가하면 되는" 문제가 아니었습니다. 환경별로 다른 보안 요구사항, 브라우저의 보안 정책, 쿠키 동작 방식 등 고려해야 할 것들이 정말 많았습니다.
"Vercel에서는 되는데 운영에서 안 돼요"라는 문제의 답은 대부분 여기에 있었습니다. 이제는 개발자가 이런 이슈를 리포트할 때, 단순히 "CORS 에러예요"가 아니라 구체적으로 어떤 환경에서 어떤 요청이 실패하는지 파악할 수 있게 되었습니다.
QA 엔지니어로서 이런 환경별 차이를 이해하고 나니, 테스트 케이스를 설계할 때도 훨씬 체계적으로 접근할 수 있게 되었습니다. "환경 차이"라는 추상적인 개념이 구체적인 설정과 코드로 이해되니, 더 정확한 이슈 분석과 재현이 가능해진 것이죠.
다음 편에서는 이런 환경별 설정 중에서도 가장 민감한 부분인 "보안 키 관리"에 대해 다뤄보겠습니다. JWT Secret을 Git에 올려버린 그날의 악몽을 함께 나누고 싶습니다...