- 발행일
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를 걷어냈다가 되돌리는 이틀과 배포 첫날의 리라이트 삭제가 같은 자리에서 났습니다.
관련 글: 직짱건강 — 백엔드를 기다리지 않고 설문 인터페이스를 먼저 완성한 방법 · axios를 걷어냈다가 이틀 만에 되돌렸다가 다시 걷어냈다 · 배포 첫날 커밋 열 개