React

Vite(로컬)와 Nginx Proxy의 차이(feat. 401, CORS)

연신내고독한늑대 2026. 2. 12. 15:09

React + Nginx + Spring API에서 401이 발생한 이유

(Vite 로컬 환경과 Nginx Proxy의 차이 정리)

이번에 React(Vite) 프로젝트를 개발 서버에 배포하면서 이상한 현상을 하나 만났다.

로컬에서는 잘 되던 API 호출이 nginx를 통해 배포하니 401이 발생했다ㅜㅜ

처음에는 단순 인증 문제라고 생각했다.
하지만 파고들어 보니, 단순한 문제가 아니었다.

■ 상황 정리

- 프론트엔드: React + Vite
- 로컬 환경: Vite dev server 사용
- 개발 서버: Docker + nginx
- API 서버: 별도 Spring 서버

로컬에서는 /v1/orgChrt 호출이 잘 된다.
하지만 nginx를 거쳐 호출하면 401이 발생한다.

■ 처음에 의심했던 것들

- Authorization 헤더가 안 넘어가나?
- Cookie가 전달되지 않나?
- Host 헤더 mismatch 문제인가?
- vite.config.ts의 changeOrigin: true 때문인가?

■ 결정적 단서: 직접 fetch 해봤을 때

브라우저에서 API를 직접 호출해봤다.

fetch("http://ip:port/api/methodName", {
  headers: {
    Authorization: "Bearer ..."
  }
})

그 결과: No 'Access-Control-Allow-Origin' header is present

=> 이건 인증 문제가 아니라 CORS 문제다.

■ 왜 로컬(Vite)에서는 문제가 없었을까?

여기서 중요한 차이가 있다.

// 로컬 환경 (Vite dev server 사용)
브라우저 → localhost:3000 (Vite)
localhost:3000 → API 서버

→ 브라우저 입장에서는 같은 origin이다. 즉, CORS 검사를 하지 않는다.

// nginx 배포 환경
브라우저 → localhost:3001 (nginx)
브라우저 → API 서버 (직접 요청 시)

→ 이때 브라우저는 Origin 헤더를 보낸다.

Spring 서버가 이를 허용하지 않으면 차단한다. 즉, 401처럼 보였지만 실제 원인은 CORS 정책이었다.

■ 깨달음

-Vite proxy는 개발 편의용 기능이다.
-changeOrigin: true는 Vite 전용 옵션이다.
-nginx는 자동으로 Origin을 바꿔주지 않는다.
-API 서버를 직접 호출하면 CORS가 발생한다.

■ 해결 방안

1. API 서버를 수정할 수 없는 상황이었다.
2. nginx proxy를 통해 CORS를 우회한다. (브라우저 → nginx → API 서버)
두가지 방법이 있지만 나는 2번을 통해서 문제를 해결했다.

최종 nginx.conf 설정

server {
  listen 3001;

  root /usr/share/nginx/html;
  index index.html;

  location /api/ {
      proxy_pass http://ip:port;

      proxy_set_header Host ip:port;
      proxy_set_header X-Real-IP $remote_addr;
      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

      proxy_set_header Authorization $http_authorization;
      proxy_set_header Cookie $http_cookie;

      proxy_pass_request_headers on;
  }

  location / {
      try_files $uri $uri/ /index.html;
  }
}

이렇게 하면 브라우저는 API 서버를 직접 알지 못한다.
CORS는 발생하지 않는다.

■ 추가 궁금증

왜 주소창에서 직접 API 치면 되는데, fetch는 CORS가 걸릴까?

브라우저 주소창에
http://ip:port/api/methodName

를 입력하면 정상 응답이 나온다.

그런데 콘솔에서 fetch로 호출하면 CORS가 뜬다.

왜 그럴까?

 

핵심 차이: “페이지 이동” vs “JS 요청”

 

1. 주소창 직접 접근

브라우저는 이를 페이지 이동(navigation) 으로 처리한다.
이 경우에는
- CORS 검사하지 않는다
- 그냥 응답을 화면에 렌더링한다

 

2. fetch / axios 요청

fetch("http://ip:port/api/methodName")

이거는 아래와 같이 요청하는 것이다.
현재 페이지(origin: http://localhost:3001) → 다른 서버(origin: http://10.0.0.5:8080)

이때 브라우저는 자동으로 다음 헤더를 붙인다.

Origin: http://localhost:3001

그렇게 되면 API 서버가 아래와 같이 받게 됨으로써 응답하지 않으면 브라우저가 차단한다.

Access-Control-Allow-Origin: http://localhost:3001

 

왜 이런 보안 정책이 존재하는 이유
악성 사이트가 사용자의 인증된 API를 몰래 호출하지 못하게 하기 위함.
예를 들어:
- 사용자가 은행 사이트에 로그인 상태
- 다른 악성 사이트가 JS로 은행 API 호출
- 세션 쿠키로 이체 시도
이걸 막기 위한 것이 CORS다.

■ 결론

  • 401이 항상 인증 문제는 아니다.
  • 브라우저 콘솔에 CORS 에러가 보이면 구조 문제다.
  • Vite proxy와 nginx proxy는 완전히 다르다.
  • 주소창 요청과 fetch 요청은 브라우저 동작 방식이 다르다.
  • API 서버를 수정 못 하면 nginx proxy로 해결 가능하다.

이번 문제는 단순 해결이 아니라
프론트 → nginx → API 서버 통신 구조를 이해하게 만든 경험이었다.