Search

Proxy

Vite ν”„λ‘μ‹œ(Proxy) μ„€μ • κ°€μ΄λ“œ

1. ν”„λ‘μ‹œ(Proxy)λž€?

ν”„λ‘μ‹œλŠ” ν΄λΌμ΄μ–ΈνŠΈμ™€ μ„œλ²„ μ‚¬μ΄μ—μ„œ μš”μ²­μ„ μ€‘κ³„ν•˜λŠ” 쀑간 μ„œλ²„μž…λ‹ˆλ‹€.
[λΈŒλΌμš°μ €] β†’ [Vite Dev Server (ν”„λ‘μ‹œ)] β†’ [λ°±μ—”λ“œ API μ„œλ²„] :5173 :5173/api/... :8080
Plain Text
볡사

μ™œ ν”„λ‘μ‹œκ°€ ν•„μš”ν•œκ°€? - CORS 문제

λΈŒλΌμš°μ €λŠ” 동일 좜처 μ •μ±…(Same-Origin Policy) 에 μ˜ν•΄ λ‹€λ₯Έ 도메인/포트둜의 μš”μ²­μ„ μ°¨λ‹¨ν•©λ‹ˆλ‹€.
ν•­λͺ©
ν”„λ‘ νŠΈμ—”λ“œ
λ°±μ—”λ“œ
μ£Όμ†Œ
http://localhost:5173
http://localhost:8080
포트
5173
8080
κ²°κ³Ό
좜처(Origin)κ°€ 닀름 β†’ CORS μ—λŸ¬ λ°œμƒ
ν”„λ‘μ‹œλ₯Ό μ‚¬μš©ν•˜λ©΄: λΈŒλΌμš°μ €λŠ” 같은 포트(5173)둜 μš”μ²­ν•˜λ―€λ‘œ CORS λ¬Έμ œκ°€ λ°œμƒν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
λΈŒλΌμš°μ € β†’ /api/member/list (5173) β†’ Viteκ°€ 8080으둜 전달 β†’ 응닡 λ°˜ν™˜
Plain Text
볡사

2. Vite ν”„λ‘μ‹œ κΈ°λ³Έ μ„€μ •

vite.config.js 파일의 server.proxy μ˜΅μ…˜μœΌλ‘œ μ„€μ •ν•©λ‹ˆλ‹€.
// vite.config.js import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import tailwindcss from '@tailwindcss/vite' export default defineConfig({ plugins: [react(), tailwindcss()], server: { proxy: { '/api': { // β‘  ν”„λ‘μ‹œ 경둜 접두사 target: '<http://localhost:8080>', // β‘‘ μ‹€μ œ λ°±μ—”λ“œ μ„œλ²„ μ£Όμ†Œ changeOrigin: true, // β‘’ Origin 헀더 λ³€κ²½ rewrite: (path) => path.replace(/^\\/api/, ''), // β‘£ 경둜 μž¬μž‘μ„± configure: (proxy) => { // β‘€ μΆ”κ°€ μ„€μ • proxy.on('proxyReq', (proxyReq) => { proxyReq.removeHeader('origin') // Origin 헀더 제거 }) } } } } })
JavaScript
볡사

3. μ„€μ • μ˜΅μ…˜ 상세 μ„€λͺ…

β‘  ν”„λ‘μ‹œ 경둜 접두사 (/api)

'/api': { ... }
JavaScript
볡사
β€’
/api둜 μ‹œμž‘ν•˜λŠ” λͺ¨λ“  μš”μ²­μ„ 이 ν”„λ‘μ‹œ κ·œμΉ™μœΌλ‘œ μ²˜λ¦¬ν•©λ‹ˆλ‹€.
β€’
예: /api/member/list, /api/board/write λ“±

β‘‘ target - λ°±μ—”λ“œ μ„œλ²„ μ£Όμ†Œ

target: '<http://localhost:8080>'
JavaScript
볡사
β€’
μ‹€μ œ API μš”μ²­μ„ 전달할 λ°±μ—”λ“œ μ„œλ²„μ˜ μ£Όμ†Œμž…λ‹ˆλ‹€.
β€’
개발 ν™˜κ²½μ—μ„œλŠ” 보톡 둜컬 μŠ€ν”„λ§λΆ€νŠΈ μ„œλ²„ μ£Όμ†Œλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€.

β‘’ changeOrigin - Origin 헀더 λ³€κ²½

changeOrigin: true
JavaScript
볡사
κ°’
λ™μž‘
true
Host 헀더λ₯Ό target μ„œλ²„μ˜ μ£Όμ†Œλ‘œ λ³€κ²½
false (κΈ°λ³Έ)
μ›λž˜ Host 헀더(localhost:5173) μœ μ§€
β€’
CORS 처리 μ‹œ 거의 항상 true둜 μ„€μ •ν•©λ‹ˆλ‹€.
β€’
λ°±μ—”λ“œ μ„œλ²„κ°€ Host 헀더λ₯Ό 검증할 λ•Œ ν•„μš”ν•©λ‹ˆλ‹€.

β‘£ rewrite - 경둜 μž¬μž‘μ„±

rewrite: (path) => path.replace(/^\\/api/, '')
JavaScript
볡사
λΈŒλΌμš°μ € μš”μ²­ URL
λ°±μ—”λ“œ 전달 URL
/api/member/list
/member/list
/api/board/write
/board/write
/api/login
/login
β€’
μ•žμ˜ /api 접두사λ₯Ό μ œκ±°ν•œ λ’€ λ°±μ—”λ“œλ‘œ μ „λ‹¬ν•©λ‹ˆλ‹€.
β€’
λ°±μ—”λ“œ API κ²½λ‘œμ— /apiκ°€ μ—†λŠ” 경우 μ‚¬μš©ν•©λ‹ˆλ‹€.
rewrite μ‚¬μš© μ—¬λΆ€ 비ꡐ
rewrite μžˆλŠ” 경우: /api/member/list β†’ /member/list (접두사 제거) rewrite μ—†λŠ” 경우: /api/member/list β†’ /api/member/list (κ·ΈλŒ€λ‘œ 전달)
Plain Text
볡사

β‘€ configure - ν”„λ‘μ‹œ 이벀트 ν›…

configure: (proxy) => { proxy.on('proxyReq', (proxyReq) => { proxyReq.removeHeader('origin') }) }
JavaScript
볡사
β€’
http-proxy 라이브러리의 이벀트λ₯Ό 직접 μ œμ–΄ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
β€’
proxyReq: λ°±μ—”λ“œλ‘œ λ‚˜κ°€λŠ” μš”μ²­μ„ κ°€λ‘œμ±„λŠ” 이벀트

removeHeader('origin')이 ν•„μš”ν•œ 이유

일뢀 λ°±μ—”λ“œ μ„œλ²„(Spring Security λ“±)κ°€ Origin 헀더λ₯Ό μ—„κ²©ν•˜κ²Œ κ²€μ‚¬ν•©λ‹ˆλ‹€.
changeOrigin: true 만으둜 ν•΄κ²°λ˜μ§€ μ•Šμ„ λ•Œ, Origin 헀더 자체λ₯Ό μ œκ±°ν•˜λ©΄
λ°±μ—”λ“œκ°€ CORS 검사λ₯Ό μš°νšŒν•˜κ²Œ λ©λ‹ˆλ‹€.
Origin 헀더 μžˆμ„ λ•Œ: λ°±μ—”λ“œκ°€ CORS μ •μ±… 검사 β†’ κ±°λΆ€ κ°€λŠ₯ Origin 헀더 제거: λ°±μ—”λ“œκ°€ 일반 HTTP μš”μ²­μœΌλ‘œ 인식 β†’ ν—ˆμš©
Plain Text
볡사

4. μš”μ²­ 흐름 전체 μ˜ˆμ‹œ

β‘  μ»΄ν¬λ„ŒνŠΈμ—μ„œ fetch('/api/member/list') 호좜 ↓ β‘‘ Vite Dev Serverκ°€ '/api' νŒ¨ν„΄ 감지 ↓ β‘’ rewrite: '/api/member/list' β†’ '/member/list' ↓ β‘£ changeOrigin: Host 헀더λ₯Ό 'localhost:8080'으둜 λ³€κ²½ ↓ β‘€ configure: Origin 헀더 제거 ↓ β‘₯ '<http://localhost:8080/member/list>' 둜 μš”μ²­ 전달 ↓ ⑦ μŠ€ν”„λ§λΆ€νŠΈ μ„œλ²„ 처리 ν›„ 응닡 λ°˜ν™˜ ↓ β‘§ Viteκ°€ λΈŒλΌμš°μ €λ‘œ 응닡 전달
Plain Text
볡사

5. React μ½”λ“œμ—μ„œ API 호좜 방법

ν”„λ‘μ‹œ μ„€μ • ν›„μ—λŠ” 전체 URL λŒ€μ‹  μƒλŒ€ 경둜만 μ‚¬μš©ν•©λ‹ˆλ‹€.
// ❌ 잘λͺ»λœ 방법 - 직접 λ°±μ—”λ“œ μ£Όμ†Œ μ‚¬μš© (CORS μ—λŸ¬) const res = await fetch('<http://localhost:8080/member/list>'); // βœ… μ˜¬λ°”λ₯Έ 방법 - μƒλŒ€ 경둜 μ‚¬μš© (Vite ν”„λ‘μ‹œκ°€ 쀑계) const res = await fetch('/api/member/list');
JavaScript
볡사

axios μ‚¬μš© μ‹œ

// axios κΈ°λ³Έ μ„€μ • (baseURL μ„€μ • λΆˆν•„μš”) import axios from 'axios'; // μš”μ²­ μ˜ˆμ‹œ const res = await axios.get('/api/member/list'); const res = await axios.post('/api/member/login', { id, pwd });
JavaScript
볡사

6. 닀쀑 ν”„λ‘μ‹œ μ„€μ •

μ—¬λŸ¬ κ²½λ‘œμ— λŒ€ν•΄ 각각 λ‹€λ₯Έ μ„œλ²„λ‘œ ν”„λ‘μ‹œν•  수 μžˆμŠ΅λ‹ˆλ‹€.
server: { proxy: { '/api': { target: '<http://localhost:8080>', // 메인 λ°±μ—”λ“œ changeOrigin: true, rewrite: (path) => path.replace(/^\\/api/, '') }, '/auth': { target: '<http://localhost:9090>', // 인증 μ„œλ²„ changeOrigin: true, rewrite: (path) => path.replace(/^\\/auth/, '') }, '/upload': { target: '<http://localhost:8080>', // 파일 μ—…λ‘œλ“œ changeOrigin: true, } } }
JavaScript
볡사

7. 개발 vs 운영 ν™˜κ²½

ν”„λ‘μ‹œ 섀정은 개발 ν™˜κ²½μ—μ„œλ§Œ λ™μž‘ν•©λ‹ˆλ‹€.
ν™˜κ²½
방법
개발 (npm run dev)
Vite ν”„λ‘μ‹œ μ‚¬μš©
운영 (npm run build ν›„ 배포)
Nginx λ¦¬λ²„μŠ€ ν”„λ‘μ‹œ λ˜λŠ” λ°±μ—”λ“œ CORS μ„€μ •

운영 ν™˜κ²½ Nginx μ˜ˆμ‹œ

location /api/ { proxy_pass <http://backend:8080/>; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; }
Plain Text
볡사

8. νŠΈλŸ¬λΈ”μŠˆνŒ…

증상
원인
ν•΄κ²° 방법
CORS error 계속 λ°œμƒ
ν”„λ‘μ‹œ 경둜 뢈일치
fetch('/api/...') κ²½λ‘œμ™€ ν”„λ‘μ‹œ ν‚€('/api') 확인
404 Not Found
rewrite μ„€μ • 였λ₯˜
λ°±μ—”λ“œ μ‹€μ œ κ²½λ‘œμ™€ rewrite κ²°κ³Ό 비ꡐ
401 Unauthorized
Origin 헀더 문제
configureμ—μ„œ removeHeader('origin') μΆ”κ°€
ν”„λ‘μ‹œ 적용 μ•ˆ 됨
μ ˆλŒ€ URL μ‚¬μš©
http://localhost:8080/... β†’ /api/... 둜 λ³€κ²½
μ„€μ • λ³€κ²½ 미적용
Vite μ„œλ²„ λ―Έμž¬μ‹œμž‘
npm run dev μž¬μ‹€ν–‰

핡심 정리

ν”„λ‘μ‹œ = CORS 문제λ₯Ό 개발 ν™˜κ²½μ—μ„œ μš°νšŒν•˜λŠ” 도ꡬ /api/* μš”μ²­ β†’ Vite Dev Server β†’ <http://localhost:8080/*> (λΈŒλΌμš°μ €) (쀑간 μ€‘κ³„μž) (Spring Boot) μ„€μ • μœ„μΉ˜: vite.config.js > server > proxy 호좜 방법: fetch('/api/경둜') β€” μ ˆλŒ€ URL μ‚¬μš© κΈˆμ§€
Plain Text
볡사