# CORS 문제 해결 방법 ## 문제 상황 프론트엔드(`http://localhost:3000`)에서 백엔드 Content API(`http://localhost:8084`)를 직접 호출하면 **CORS(Cross-Origin Resource Sharing)** 에러가 발생했습니다. ### 에러 메시지 ``` Network Error AxiosError: Network Error code: "ERR_NETWORK" ``` ### 원인 분석 ```bash # CORS preflight 요청 테스트 curl -X OPTIONS http://localhost:8084/api/v1/content/images/generate \ -H 'Origin: http://localhost:3000' \ -H 'Access-Control-Request-Method: POST' \ -H 'Access-Control-Request-Headers: content-type' # 결과: HTTP/1.1 403 Forbidden # Invalid CORS request ``` 백엔드 서버가 `http://localhost:3000` origin에서의 CORS 요청을 허용하지 않음. --- ## 해결 방법: Next.js API Proxy 백엔드 CORS 설정을 수정하는 대신, **Next.js API Routes를 프록시로 사용**하여 CORS 문제를 우회했습니다. ### 아키텍처 ``` [Browser] ↓ (Same-Origin Request) [Next.js Frontend: localhost:3000] ↓ [Next.js API Proxy: /api/content/*] ↓ (Server-to-Server Request, CORS 무관) [Content API Backend: localhost:8084] ``` ### 구현 파일 #### 1. **이미지 생성 프록시** (`/api/content/images/generate/route.ts`) ```typescript export async function POST(request: NextRequest) { const body = await request.json(); const response = await fetch('http://localhost:8084/api/v1/content/images/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); return NextResponse.json(await response.json()); } ``` **URL 매핑**: - Frontend: `POST /api/content/images/generate` - Backend: `POST http://localhost:8084/api/v1/content/images/generate` #### 2. **Job 상태 조회 프록시** (`/api/content/images/jobs/[jobId]/route.ts`) ```typescript export async function GET(request: NextRequest, { params }: { params: { jobId: string } }) { const { jobId } = params; const response = await fetch(`http://localhost:8084/api/v1/content/images/jobs/${jobId}`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }); return NextResponse.json(await response.json()); } ``` **URL 매핑**: - Frontend: `GET /api/content/images/jobs/{jobId}` - Backend: `GET http://localhost:8084/api/v1/content/images/jobs/{jobId}` #### 3. **이미지 목록 조회 프록시** (`/api/content/events/[eventDraftId]/images/route.ts`) ```typescript export async function GET(request: NextRequest, { params }: { params: { eventDraftId: string } }) { const { eventDraftId } = params; const { searchParams } = new URL(request.url); let url = `http://localhost:8084/api/v1/content/events/${eventDraftId}/images`; if (searchParams.get('style')) url += `?style=${searchParams.get('style')}`; if (searchParams.get('platform')) url += `&platform=${searchParams.get('platform')}`; const response = await fetch(url, { method: 'GET', headers: { 'Content-Type': 'application/json' }, }); return NextResponse.json(await response.json()); } ``` **URL 매핑**: - Frontend: `GET /api/content/events/{eventDraftId}/images?style=SIMPLE&platform=INSTAGRAM` - Backend: `GET http://localhost:8084/api/v1/content/events/{eventDraftId}/images?style=SIMPLE&platform=INSTAGRAM` --- ## 클라이언트 코드 변경 ### Before (직접 백엔드 호출 - CORS 에러 발생) ```typescript const CONTENT_API_BASE_URL = 'http://localhost:8084'; export const contentApiClient = axios.create({ baseURL: CONTENT_API_BASE_URL, }); // ❌ CORS Error await contentApiClient.post('/api/v1/content/images/generate', request); ``` ### After (Next.js API Proxy 사용 - CORS 우회) ```typescript const CONTENT_API_BASE_URL = '/api/content'; // Same-origin request export const contentApiClient = axios.create({ baseURL: CONTENT_API_BASE_URL, }); // ✅ Works! (Same-origin → Server-side proxy → Backend) await contentApiClient.post('/images/generate', request); ``` --- ## 장점 ✅ **프론트엔드 수정만으로 해결**: 백엔드 CORS 설정 변경 불필요 ✅ **Same-Origin 정책 준수**: 브라우저는 같은 도메인으로 인식 ✅ **서버 간 통신**: Next.js 서버에서 백엔드 호출 (CORS 무관) ✅ **보안 강화**: 백엔드 URL을 클라이언트에 노출하지 않음 ✅ **환경 변수 활용**: `NEXT_PUBLIC_CONTENT_API_URL`로 배포 환경 대응 --- ## 프로덕션 배포 시 고려사항 ### 환경 변수 설정 ```bash # .env.local (개발 환경) NEXT_PUBLIC_CONTENT_API_URL=http://localhost:8084 # .env.production (프로덕션 환경) NEXT_PUBLIC_CONTENT_API_URL=https://api.production.com ``` ### 프록시 코드에 적용 ```typescript const CONTENT_API_BASE_URL = process.env.NEXT_PUBLIC_CONTENT_API_URL || 'http://localhost:8084'; const response = await fetch(`${CONTENT_API_BASE_URL}/api/v1/content/images/generate`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); ``` ### 타임아웃 설정 ```typescript const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), signal: AbortSignal.timeout(120000), // 120초 타임아웃 }); ``` --- ## 테스트 방법 ### 1. 개발 서버 실행 ```bash npm run dev ``` ### 2. 브라우저에서 테스트 ``` http://localhost:3000/events/create?event-creation.step=contentPreview ``` ### 3. 네트워크 탭 확인 브라우저 개발자 도구 → Network 탭에서 다음 요청 확인: ``` POST http://localhost:3000/api/content/images/generate (Status: 202) GET http://localhost:3000/api/content/images/jobs/job-xxxxx (Status: 200) GET http://localhost:3000/api/content/events/7777/images (Status: 200) ``` 모두 **Same-Origin** 요청이므로 CORS 에러 없음! --- ## 참고 자료 - [Next.js API Routes](https://nextjs.org/docs/app/building-your-application/routing/route-handlers) - [CORS (MDN)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) - [Proxy Pattern](https://en.wikipedia.org/wiki/Proxy_pattern)