Vite에서 proxy를 쓰는데 github page 배포와 같이 base url이 필수적인 세팅에서는 어떻게 proxy 경로 구성해야 하는지 설명 찾기가 힘들어서 이번 기회에 글로 남겨놓으려 한다.
배포 환경에서도 proxy를 이용하고 싶었는데 github page에서는 지원하지 않는 것인지 실패했고, vercel의 경우 vercel.json을 통한 추가 경로 설정으로 성공했다. (참고 : https://velog.io/@jeajea0127/Vercel에서-proxy-설정하기)
netlify에서도 비슷한 추가 설정으로 가능한 것 같다 : https://github.com/vitejs/vite/discussions/17381#discussioncomment-9939768
github page는 정적 페이지 배포만 지원해서 불가능한 것 같다.. : https://github.com/vitejs/vite/discussions/15523
CORS를 프론트 단에서 해결 가능한 proxy 기능에 대한 전반적인 설명도 덧붙인다.
Proxy란?
프록시는 기존 클라이언트와 서버 사이에서 데이터나 HTTP 요청을 중계하는 역할을 한다.
처음엔 proxy가 뭔지 엄청 헷갈렸는데, 지금 보니 단순히 클라이언트 → 서버로 요청을 보낼 때 중간에서 중계하는 역할이 끼어 있으면 이 역할을 하는 친구들을 전부 통틀어서 proxy라 부르는 것으로 이해했다.
클라이언트가 요청을 보낼 때 해당 요청을 먼저 받아 검증할 수도 있고, 서버에서 받은 응답을 캐싱하거나 보안 처리를 추가로 할 수도 있다.
클라이언트 측에 있는지, 서버 측에 있는지에 따라 포워드 프록시, 리버스 프록시로 구분된다.
포워드 프록시 (Forward Proxy)
클라이언트의 요청을 받아 외부 서버로 중계한다.
회사나 학교 등 단체에서 특정 공인 IP 아래 여러 서브넷을 두고 여기서 오는 요청들을 한 번에 모아 검증한 후 외부 인터넷에 연결할 때 사용 가능하다. 같은 사이트에 대한 중복 요청은 응답을 캐싱해두었다 주는 방식으로 효율을 높일 수 있다.
프록시 서버를 별도로 두고 요청을 프록시 서버를 거쳐서 가게 하는 방식으로 원래 IP 주소를 숨길 수도 있다.
Vite Proxy 처럼 CORS 오류를 프론트 단에서 해결하는 방법으로도 쓴다.
리버스 프록시 (Reverse Proxy)
서버 측에서 동작하며 클라이언트 요청들을 먼저 받아 백엔드 서버로 전송한다.
특정 IP나 특정 포트로 오는 요청을 미리 거르는 보안 처리를 할 수도 있고, 갑자기 많이 들어오는 요청에 대해 트래픽 분산, 로드밸런싱을 해줄 수도 있다.
Vite에서 제공하는 Proxy 기능
- 그렇다면 Vite에서는 어떻게 Proxy를 제공하는가?
- 그리고 이 Proxy를 통해 어떻게 CORS를 해결하는가?
먼저 부딪힌 문제 상황은 다음과 같았다.
공공데이터 포탈에서 제공하는 API로 데이터를 프론트에서 바로 받아오고 싶은데, 당연히 공공데이터 포탈 측 서버의 Allow-origin에는 우리 프론트 서버가 등록되어 있지도 않으니 CORS 오류가 나는 것이다.
보통의 프로젝트에서는 브라우저 CORS 정책 제한이 없는 백엔드 서버에서 공공데이터 정보를 받아서 정제 후 프론트에 던져줘서 잘 발생하지 않는 문제다.
하지만 수업 플젝이라 백엔드 없이 가기로 했기 때문에.. 그냥 Mock Data 쓸 걸.. 왜 실제 데이터 받아오려다
이런 문제를 Vite Proxy를 통해 해결할 수 있다.
원리를 요약하면 결국 브라우저에서 CORS를 거는 게 문제이므로, Vite에서 브라우저를 거치지 않고 바로 원하는 API 서버 주소와 우리 프론트의 특정 주소를 매핑시켜주는 것이다.
그러면 겉에서 보기에 우리 브라우저는 orgin이 같은 자신의 프론트 주소로 request를 날리는 것으로 인식하고 CORS를 뿜지 않는다!!
브라우저 (origin: 내프론트주소) Request → https://내프론트주소/api (Vite에서 해당 주소에 백엔드 주소를 매핑시켜서 Response를 받아온다)
이 원리를 설명한 글도 못 찾고 proxy에 대한 이해도 부족한 처음엔 왜 이렇게 작동하는지 알기 힘들었다.
Vite 공식문서 예시
vite.config.ts 파일
export default defineConfig({
server: {
proxy: {
// 문자열만: ->
'/foo': '',
// 옵션과 함께: http://localhost:5173/api/bar-> <http://jsonplaceholder.typicode.com/bar>
'/api': {
target: '<http://jsonplaceholder.typicode.com>',
changeOrigin: true,
rewrite: (path) => path.replace(/^\\/api/, '')
},
// 정규식(RegExp)과 함께: -> <http://jsonplaceholder.typicode.com/>
'^/fallback/.*': {
target: '<http://jsonplaceholder.typicode.com>',
changeOrigin: true,
rewrite: (path) => path.replace(/^\\/fallback/, '')
},
// 프락시 인스턴스 사용
'/api': {
target: '<http://jsonplaceholder.typicode.com>',
changeOrigin: true,
configure: (proxy, options) => {
// proxy 변수에는 'http-proxy'의 인스턴스가 전달됩니다
}
},
// 웹소켓 또는 socket.io 프락시: ws://localhost:5173/socket.io -> ws://localhost:5174/socket.io
// `rewriteWsOrigin`을 사용할 때는 주의하세요. CSRF 공격에 노출될 수 있습니다.
'/socket.io': {
target: 'ws://localhost:5174',
ws: true,
rewriteWsOrigin: true,
}
}
}
})
공식 문서에서 위와 같은 예시를 줬는데, /api 를 붙이는 첫 예시를 보면 http://localhost:5173/api 로 오는 모든 요청을 target 주소로 매핑시키겠다는 것이다.
만약 rewrite 옵션이 없으면 http://jsonplaceholder.typicode.com/api 로 요청이 가겠지만, /api는 우리가 프론트에서 경로를 구분하려 임의로 준 것이므로 rewrite에서 /api 경로만 제거해줘 실제 요청에선 없앤다.
fetch 코드 부분
const response = await fetch(
`/api/bar?${queryString}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
proxy 처리된 api를 fetch로 받는 예시는 위와 같다. 프론트 자신 서버의 /api/bar 경로로 요청을 보내는 것과 동일하고, 브라우저에서도 origin은 모두 자기자신으로 뜨지만 Vite에서 백엔드 경로와 매핑시켜주는 것이다!!
base url이 있을 때 Vite Proxy 설정
문제는 base url이 존재할 때 VIte Proxy 설정을 어떻게 해야 하느냐는 것이다.
공식 문서에도 설명이 없고, 이 문제를 다룬 블로그도 못 찾아서 열심히 열심히 경로 이리저리 다 바꾸면서 실험하면서 알아냈다.
vite.config.ts 파일
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import { TanStackRouterVite } from '@tanstack/router-plugin/vite';
import svgr from 'vite-plugin-svgr';
const isTest = process.env.NODE_ENV === 'test';
// <https://vite.dev/config/>
export default defineConfig({
plugins: [svgr(), !isTest && TanStackRouterVite(), react()],
base: '/uxplorers-frontend/',
server: {
proxy: {
'/uxplorers-frontend/api/ExpBusArrInfoService': {
target: '<http://apis.data.go.kr/1613000>',
changeOrigin: true,
rewrite: (path) => path.replace(/^\\/uxplorers-frontend\\/api/, ''),
},
'/uxplorers-frontend/api/ExpBusInfoService': {
target: '<http://apis.data.go.kr/1613000>',
changeOrigin: true,
rewrite: (path) => path.replace(/^\\/uxplorers-frontend\\/api/, ''),
},
// '/uxplorers-frontend/api': {
// target: '<http://apis.data.go.kr/1613000>',
// changeOrigin: true,
// rewrite: (path) => path.replace(/^\\/api/, ''),
// },
},
},
});
fetch 코드 부분
const response = await fetch(
`/uxplorers-frontend/api/ExpBusArrInfoService/getExpBusArrPrdtInfo?${queryString}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
}
);
바로 위와 같이 작성하면 된다.
처음엔 base url을 vite.config.ts에서 설정해주니까 fetch 보낼 때나 proxy 설정할 때 앞에 자동으로 붙여주는게 아닐까 추측했는데, 이게 아니었다!!
앞에 자동으로 붙여주지 않으므로, 직접 base url을 proxy 등록 시, fetch request url 맨 앞에 추가해주어야 한다!!!
그리고 당연히 이는 우리 프론트에서만 사용되는 url route 부분이므로, 실제 요청 보낼 때 제거를 위해 path.replace 구문에도 추가해준다.
즉, http://myfrontserver:5173/base-url/api/route 로 보내는 request를 http://target-api-url/route 로 매핑시켜 준다는 것이고, fetch('/base-url/api/route?${queryString}'); 으로 전송해야 한다는 것이다!!
게다가 vite.config.ts 파일 내에서 환경변수를 불러오려면 import.meta.env가 아닌, process.env를 써야 하는 것 같다.
TODO
- CORS에 대한 블로그 글도 추가로 작성해야겠다..
- github pages 배포에서 proxy 이용 방법도 찾아봐야겠다
참고 자료
'프로그래밍 > Frontend (React, Javascript, Typescript)' 카테고리의 다른 글
전역 상태(zustand)에서 배열 concat의 side effect, 해결 방안 (feat. React Strict Mode) (0) | 2024.12.15 |
---|---|
10분 만에 웹사이트에 PWA 적용해서 웹앱 만들기! (2) | 2024.11.14 |
React Component와 객체지향의 차이에 관하여 (2) | 2024.09.29 |
Fixed Menu, 메뉴 바 상단 고정 구현 시 content에 margin-top을 주면 위험한 이유 (feat. margin collapsing) (4) | 2024.09.28 |
[JavaScript, Node.js] 프론트엔드(JavaScript Runtime)에서의 Race Condition (0) | 2024.04.11 |