Next.js 버전업 이후 셀프 호스팅 환경에서의 CORS
안녕하세요. 크리에이트립(Creatrip)에서 프론트엔드 개발을 하는 서종학입니다. 오늘은 Next.js 13 버전업 이후 겪었던 CORS 이슈에 대한 경험을 공유하려고 해요.
얼마 전 정기배포 직후, 저희 크리에이트립의 일부 도메인에서 웹 동작에 필요한 일부 script 파일을 받아오지 못해 페이지가 제대로 동작하지 않는 이슈가 발생했어요. 😢
문제가 발생하는 페이지들의 CORS 에러를 확인했는데, 정기 배포 직전까지는 문제없이 동작하고 있던 곳이었어요. 특이하게도 creatrip.com
에서의 접속은 아무런 이슈가 없었지만 www.creatrip.com
도메인에서의 접속에서 문제가 발생했어요.
저희 크리에이트립에서는 Next.js 빌드 이후 생성된 정적 파일들을 Cloudfront로 제공하고 있었고, 원인을 파악하기에 앞서 접속 오류 문제를 빠르게 해결하고자 Cloudfront의 응답 헤더 정책을 수정했어요.
누락되어 있던 응답 헤더 정책에, creatrip의 두 도메인 www.creatrip.com, creatrip.com
을 Access-Control-Allow-Origin으로 추가하는 커스텀 정책을 만들어 넣어 주는 방식으로 우선 대응했습니다.
개인적으로는 매우 오랜만에 겪은 CORS 에러라서 당황했습니다. 이 시점에는 해당 이슈가 Next 버전업으로 말미암은 이슈라고 전혀 생각하지 못했어요.
기존 Next.js 버전인 12.3.4에서 13.5.6으로 업데이트를 하면서 릴리즈 노트를 자세히 살펴보았고 개발 환경과 스테이징 환경에서의 테스트도 꼼꼼하게 진행했기 때문이에요. 따라서 저희는 Cloudfront 쪽의 이슈 혹은 기타 외부 요인에 대한 가능성에 비중을 두고 있었어요.
그런데 본격적으로 원인을 찾아보던 중, 팀원의 도움으로 원인에 대해 추측할 수 있는 Next.js 이슈 문서를 찾게 되었습니다.
위 문서와 저희 프로덕트의 재현을 바탕으로 파악한 이슈 발생에 대한 대략적인 내용은 다음과 같아요.
Next.js 12.3.4 버전에서는 CSR(Client Side Rendering)로 제공되는 페이지들의 script 태그에 crossorigin
속성이 없었어요.
그런데 13.5.6 버전 업데이트 이후에는 crossorigin
속성이 추가된 것을 볼 수 있습니다.
<audio>, <img>, <link>, <script> <video>
와 같은 html tag는 기본적으로 외부 도메인의 리소스를 가져와 제공할 수 있어요. 서로 다른 서버의 리소스를 공유하기 위해 특별한 처리를 할 필요가 없는 셈이죠.
crossorigin
속성은 <audio>, <img>, <link>, <script> <video>
와 같은 element에서 해당 요소가 CORS 요청을 처리하는 방식을 명시하여 리소스에 대한 CORS를 활성화할 수 있어요.
crossorigin
속성을 추가하고 빈 문자열 혹은 아무 값을 넣지 않으면, 기본값인 anonymous
로 동작하며 해당 element의 CORS 요청의 credentials flag가 same-origin
으로 지정되어요.
Next.js에서 script element에 사용되는 crossorigin
속성은 next.config에서 직접 정의할 수 있는데요, 12.3.4 버전의 Next.js 소스코드를 살펴보면 next.config 내부의 crossOrigin을 사용하여 렌더링하는 곳을 살펴볼 수 있습니다.
당연하게도 next.config의 crossOrigin 속성을 명시적으로 할당하지 않았다면 해당 값은 기본값인 undefined
로 넘어가게 됩니다.
그렇다면 13.5.6 버전에서는 어떻게 동작할까요?
13.5.6에서는 config의 crossOrigin에 대해 빈 문자열로 fall back 처리가 되어있는 것을 볼 수 있어요.
이 변경사항은 아래 PR에서 이뤄졌는데요, 작업의 내용과 커밋을 살펴 보았을 때 의도한 변경사항이 아닌 단순 실수로 추측되어요.
https://github.com/vercel/next.js/pull/55622
위 수정사항은 13.5.3에 반영되었어요. crossOrigin 속성의 기본값이 빈 문자열이 됨에 따라 script tag에 기본적으로 crossorigin
속성이 붙게 된 거에요.
따라서 별도의 CORS 처리 헤더를 지정해두지 않은 www.creatrip.com
에서의 요청이 crossorigin
속성과 함께 동작하게 되면서 CORS 이슈가 발생했다는 것을 알 수 있었어요.
이는 명백한 버그인데요, 현재 코드 구현에 따르면 next.config에서 crossOrigin 속성을 사용하지 않기 위해 false
를 명시하더라도 || ''
처리로 항상 기본값인 crossorigin
적용이 되기 때문이에요.
현시점 가장 최근 릴리즈 버전인 14.1.2 에서도 아직 해당 문제는 수정되지 않았고, 대응 PR은 이미 작년 말에 올라온 상태이지만 아직은 병합이 되지 않은 상태로 보류 중이에요. (하지만 곧 될 것 같아요)
배포 직후 제보된 이슈에 대한 대응은 Cloudfront 응답 헤더 정책을 수정하며 빠르게 끝났지만, 정확히 어떤 부분에서 이슈가 발생했고 원인이 무엇인지, 재발 가능한 요소가 있는지를 파악하는 데 시간을 사용하는 것은 매우 중요하다고 생각해요.
저희 팀은 다양한 이슈에 대해 집요할 정도의 원인 파악과 이해를 권장하고 있는데요, 궁극적으로는 이러한 이해가 재발 방지를 막아 프로덕트의 견고함을 유지하고 개개인의 성장에도 도움이 된다고 생각하기 때문이에요. 그 과정에서 파악하거나 새로 배운 것들을 팀 내에서 공유하는 활동도 적극 격려하고 있어요.
정해진 리소스에서 업무를 제시간 안에 처리하는 것도 중요하지만, 구성원들이 끊임없이 성장하고 그 성장의 과실을 팀 전체에서 나누는 문화야말로 바람직한 개발 조직의 문화라고 생각해요.
이 글이 지엽적으로는 같은 이슈를 겪으신 분들에게 도움이 되고, 좀 더 넓게는 팀 내부의 이슈 트래킹 절차에 대해 한 번 더 고민하고 돌아볼 수 있는 계기가 되면 좋겠습니다.