- 발행일
CORS를 next.config.js의 headers()로 잡으려 했다 — 요청은 그 경로로 나가지도 않았다
CORS를 next.config.js의 headers()로 잡으려 했다
이 글은 2026년 9월에 당시 커밋 이력을 다시 보며 정리한 것입니다. 날짜는 작업한 날 기준입니다.
직짱건강 프런트는 Next 12 pages 라우터에 Vercel 배포였고 백엔드는 EC2 IP에 8080 포트로 떠 있었습니다. 3월 1일에 팀원이 axios 인스턴스를 만들면서 주소를 이렇게 박았어요.
const axiosInstance = axios.create({
baseURL: 'http://<EC2 IP>:8080',
withCredentials: true,
});
3월 4일 저녁, 화면에서 API를 부르니 CORS 오류가 났습니다. 어떤 요청이었는지는 커밋에 없습니다. 그날 제가 한 시도가 두 커밋으로 남아 있어요.
40330d6 2024-03-04 20:27 [24-03-04] cors 에러 수정 시도
cd74d5c 2024-03-04 21:09 [24-03-04] cors 에러 수정 시도 2차
1차 — 응답에 허용 헤더를 붙였다
next.config.js의 diff입니다.
source: "/api/:path*",
- destination: "http://<EC2 IP>:8080/api/:path*",
+ destination: "https://<EC2 IP>:8080/api/:path*",
},
];
},
+ async headers() {
+ return [
+ {
+ source: "/api/:path*",
+ headers: [
+ { key: "Access-Control-Allow-Credentials", value: "true" },
+ { key: "Access-Control-Allow-Origin", value: "*" },
+ { key: "Access-Control-Allow-Methods", value: "GET,OPTIONS,PATCH,DELETE,POST,PUT" },
+ { key: "Access-Control-Allow-Headers", value: "X-CSRF-Token, X-Requested-With, ..." },
+ ]
+ }
+ ];
+ },
두 가지를 한 번에 바꿨습니다. 리라이트 목적지를 http에서 https로, 그리고 /api/* 응답에 Access-Control-Allow-* 네 개를 붙이는 headers()를 추가. 헤더 이름과 값의 나열이 검색 결과에서 흔히 보는 형태라 그대로 옮긴 것으로 짐작하지만, 어디서 가져왔는지는 남아 있지 않습니다.
2차 — 같은 설정을 다시 배열하고 _document까지
40분 뒤 2차 커밋은 next.config.js를 module.exports 형태로 고쳐 쓰고, headers()와 rewrites()의 순서를 바꾸고, 헤더 값을 여러 줄로 펼친 것이었습니다. 내용은 1차와 같습니다.
그리고 _document.tsx를 통째로 바꿨어요. 팀원이 2월에 만들어 둔 styled-components 서버 렌더링용 ServerStyleSheet 수집 코드를 지우고 카카오 지도 SDK를 <script>로 심는 함수형 Document로 교체했습니다. CORS와는 관계없는 파일인데 같은 커밋에 "cors 에러 수정 시도 2차"라는 제목으로 들어갔습니다. 왜 그 파일을 열었는지는 커밋에 없어요.
요청은 그 경로로 나가지 않았다
이 글을 쓰며 두 커밋 시점의 코드를 다시 보고 알게 된 것입니다.
headers()는 Next 서버가 자기 응답에 붙이는 헤더입니다. source: "/api/:path*"이니 브라우저가 Next 서버의 /api/...를 불렀을 때 그 응답에 붙어요. 그런데 axios의 baseURL은 http://<EC2 IP>:8080이었습니다. 요청은 Vercel을 거치지 않고 EC2로 바로 갔고 브라우저가 CORS를 검사한 응답은 EC2가 준 응답이었습니다. Next 설정을 아무리 바꿔도 그 응답에는 손이 닿지 않아요.
| 경로 | 그때 실제 | Next 설정이 닿는 곳 |
|---|---|---|
| 브라우저 → EC2 IP 직접 | axios가 이 경로 | 안 닿음 |
브라우저 → Vercel /api → EC2 (리라이트) | 아무도 안 부름 | 여기만 닿음 |
리라이트가 진짜 해결책이 될 수는 있었습니다. 브라우저 입장에서는 같은 출처(/api)를 부르는 거라 CORS 자체가 안 생기니까요. 그러려면 baseURL을 /api나 빈 값으로 바꿔야 했는데, 저는 next.config.js만 보고 있었습니다. 프록시가 어디서 어디로 가는지 그림이 없었던 거예요.
http를 https로 바꾼 것도 별개의 문제였습니다. IP 주소에 https로 붙으려면 그 IP로 발급된 인증서가 있어야 하는데, 그게 있었는지는 확인한 기록이 없어요. Vercel의 https 페이지에서 http API를 부르면 브라우저가 mixed content로 막는 것도 CORS와 다른 오류입니다. 그날 화면에 뜬 오류가 셋 중 어느 것이었는지 지금은 알 수 없습니다.
이틀 뒤 주소가 환경 변수로 옮겨 가며 끝났다
3월 6일 새벽 팀원이 baseURL을 process.env.NEXT_PUBLIC_API_URL로 바꿨고 같은 날 저녁 저도 리라이트 목적지를 같은 변수로 바꿨습니다(0123395). 환경 변수가 무엇을 가리켰는지는 저장소에 없어요. 이후 CORS 커밋은 없습니다.
headers() 블록은 지금도 next.config.js에 그대로 있습니다. 목적지는 6월에 ${process.env.NEXT_PUBLIC_API_URL}/api/:path*로 한 번 더 고쳐졌고요.
검증
그날 확인한 것.
- 두 커밋 뒤에도 오류가 남았는지, 사라졌는지 기록이 없습니다. 이틀 뒤 주소 변경 이후 CORS 관련 커밋이 없다는 것으로만 짐작합니다.
이 글을 쓰며 확인한 것.
- 두 커밋 시점의
src/api/axiosInstance.ts를git show로 열어baseURL이 IP 직접 호출이었음을 확인했습니다. baseURL이 환경 변수로 바뀐 커밋(1076bb8, 팀원)과 리라이트 목적지가 바뀐 커밋(0123395, 저) 순서를git log로 맞췄습니다.- 현재
next.config.js에Access-Control-*네 줄이 남아 있는 것,_document.tsx에ServerStyleSheet가 다시 들어온 적이 없는 것을git grep으로 확인했습니다.
남은 것 · 한계
- 오류 메시지를 안 읽고 설정을 바꿨습니다. CORS·인증서·mixed content 중 무엇이었는지 기록이 없어서 이 글도 "무엇이 아니었나"까지만 말할 수 있습니다.
- 효과 없는 설정이 2년째 남아 있습니다.
headers()는 아무 요청에도 닿지 않는데 지우지 않았어요. _document.tsx에서 지운 스타일 수집 코드를 되돌리지 않았습니다. styled-components를 서버 렌더링에서 쓰면 첫 화면에 스타일이 늦게 붙을 수 있는데, 그 영향을 확인한 적이 없습니다.- 프록시가 어디를 지나는지 그림 없이 설정만 만진 일은 여기서 끝나지 않았습니다. 여섯 달 뒤 TripTune에서 axios를 걷어냈다가 되돌리는2024-09-14axios를 걷어냈다가 이틀 만에 되돌렸다가 다시 걷어냈다 — 문제는 라이브러리가 아니라 프록시였다사이드 프로젝트에서 axios를 fetch로 바꿨다가 28시간 뒤 되돌리고, 30분 뒤 다시 fetch로 갔습니다. 그 사이 커밋 메시지는 "프록시 시도 중", "프록시 실험 일단 종료", "credentials 처리 방법 실험 시도"였어요. 되돌아보니 HTTP 클라이언트를 바꾼 게 아니라 Netlify 리다이렉트와 Next 리라이트 사이를 헤맨 이틀이었습니다. 왜 흔들렸는지를 커밋 순서대로 적습니다. 이틀과 배포 첫날의 리라이트 삭제2024-11-28배포 첫날 커밋 열 개 — www 누락, 리라이트 삭제, 그리고 읽히지도 않던 middlewareTripTune을 처음 Netlify에 올린 날, 커밋 열 개가 전부 next.config.js와 middleware.ts를 만지는 것이었습니다. 백엔드 주소에 www가 빠졌고, 리라이트를 지웠다가 되살렸고, 다음날에는 middleware의 matcher를 통째로 뒤집었어요. 지금 커밋을 다시 보니 그 middleware는 위치 때문에 애초에 실행된 적이 없었을 가능성이 큽니다.가 같은 자리에서 났습니다.
관련 글: 직짱건강 — 백엔드를 기다리지 않고 설문 인터페이스를 먼저 완성한 방법2024-03-31직짱건강 — 백엔드를 기다리지 않고 설문 인터페이스를 먼저 완성한 방법직장인 대상 건강 설문과 위치 기반 병원 추천을 묶은 헬스케어 서비스입니다. 6인 협업 프로젝트에서 프런트엔드로 참여했고, 백엔드 API가 없는 상태에서 JSON 목업으로 설문 플로우를 먼저 완성해 병렬 작업이 가능하게 만든 게 핵심 기여였어요. · axios를 걷어냈다가 이틀 만에 되돌렸다가 다시 걷어냈다2024-09-14axios를 걷어냈다가 이틀 만에 되돌렸다가 다시 걷어냈다 — 문제는 라이브러리가 아니라 프록시였다사이드 프로젝트에서 axios를 fetch로 바꿨다가 28시간 뒤 되돌리고, 30분 뒤 다시 fetch로 갔습니다. 그 사이 커밋 메시지는 "프록시 시도 중", "프록시 실험 일단 종료", "credentials 처리 방법 실험 시도"였어요. 되돌아보니 HTTP 클라이언트를 바꾼 게 아니라 Netlify 리다이렉트와 Next 리라이트 사이를 헤맨 이틀이었습니다. 왜 흔들렸는지를 커밋 순서대로 적습니다. · 배포 첫날 커밋 열 개2024-11-28배포 첫날 커밋 열 개 — www 누락, 리라이트 삭제, 그리고 읽히지도 않던 middlewareTripTune을 처음 Netlify에 올린 날, 커밋 열 개가 전부 next.config.js와 middleware.ts를 만지는 것이었습니다. 백엔드 주소에 www가 빠졌고, 리라이트를 지웠다가 되살렸고, 다음날에는 middleware의 matcher를 통째로 뒤집었어요. 지금 커밋을 다시 보니 그 middleware는 위치 때문에 애초에 실행된 적이 없었을 가능성이 큽니다.