카카오페이지 웹에서 정주행 모드 옵션이 유지되지 않던 문제의 원인을 끝까지 파고든 글의 파트 2다. 브라우저 캐시를 쓸 때만 swiper CSS에서 CORS 에러가 나던 현상을 Next.js의 CSS 처리 방식과 브라우저별 캐시 정책까지 내려가 규명한다. 최종 원인은 link 태그로 받은 캐시 응답을 GET Fetch 요청이 재사용하는 크롬·사파리의 동작이었다.
핵심 포인트- link 태그로 CSS를 가져올 때는 CORS 정책이 적용되지 않아 관련 헤더 자체가 없다.
- 전역에서 임포트한 CSS는 하나의 파일로 합쳐지지만 컴포넌트 내부에서 임포트하면 별도 파일로 분리된다.
- 분리된 CSS는 Client-Side Navigation 시 GET Fetch로 받아오는데 이 요청은 CORS 정책을 따른다.
- 크롬과 사파리는 link 태그 요청과 GET Fetch 요청을 같은 요청으로 보고 캐시를 재사용하지만 파이어폭스는 별개로 본다.
- 그래서 캐시에 있던 CORS 헤더 없는 응답이 재사용되며 정책 위반으로 판정됐다.
- 해결책은 전역 임포트로 되돌리거나 next.config의 crossOrigin 옵션으로 link 태그에 crossorigin을 붙이는 것이다.
상세 정리- 출발점: 파트 1에서 주먹구구식으로 고쳤던 두 변경 중 컴포넌트 내부 require를 import로 바꾼 것만으로는 에러가 계속 났고, 전역으로 옮긴 쪽이 실제로 듣는 조치였다.
- 첫 단서: 그렇다면 컴포넌트 내부 임포트에서만 예상 밖의 동작이 추가로 일어난다는 뜻이 된다.
- Next.js 문서 확인: 전역에 선언된 CSS는 production에서 하나의 압축된 파일로 합쳐진다고 명시돼 있다.
- 실측 1: 실제 배포된 페이지의 헤더에 걸린 CSS 파일을 열어보니 문제의 CSS 내용이 다른 CSS와 함께 합쳐져 있었다.
- 실측 2: 컴포넌트 내부 임포트로 바꾸자 그 파일에서 해당 내용이 사라졌고, 컴포넌트가 렌더링되는 뷰어 페이지에서만 별도 CSS로 나타났다.
- 도출된 사실: 컴포넌트 내부에서 임포트한 CSS는 별도 파일로 남고 그 컴포넌트가 그려지는 페이지에 진입할 때 가져온다.
- Code Splitting: Next.js는 진입한 페이지에 필요한 리소스만 가져오도록 빌드 시 코드를 쪼갠다.
- 두 가지 이동 방식: 페이지 자체를 서버에 요청해 다시 그리는 Server-Side Navigation과, 필요한 리소스만 요청해 부분만 갱신하는 Client-Side Navigation이 있다.
- buildManifest: Client-Side Navigation은 페이지별 필요 리소스 목록이 담긴 buildManifest를 참고해 GET Fetch로 파일을 요청한다. 실제로 뷰어 페이지 항목에 문제의 CSS 파일이 들어 있었다.
- 정책 차이: GET Fetch는 link 태그와 달리 CORS 정책을 따르므로 이 지점에서 에러가 날 조건이 갖춰진다.
- 남은 의문: 그런데 에러는 브라우저 캐시를 쓸 때만 났고, 메시지는 응답에 Access-Control-Allow-Origin 헤더가 없다고 말하고 있었다.
- 검색으로 확인: 캐시 사용 시 CORS 헤더가 없는 상황이라는 키워드로 찾아 관련 글에서 원인과 해결법을 확인했고, 파이어폭스에서는 재현되지 않는다는 점도 직접 확인했다.
- 브라우저별 동작: 파이어폭스는 link 태그 요청과 GET Fetch 요청을 별개로 봐 새로 요청하지만, 크롬과 사파리는 같은 요청으로 보고 캐시된 응답을 그대로 쓴다.
- 최종 원인: 캐시에 있던 응답은 link 태그로 받은 것이라 CORS 헤더가 없고, 그것을 GET Fetch 요청이 재사용하니 정책 위반으로 판정된다.
- 해결책 1: 전역 임포트로 되돌리면 CSS가 하나로 합쳐져 최초 한 번만 link 태그로 받고 추가 GET Fetch가 없어 문제를 피한다.
- 해결책 2: next.config에 crossOrigin을 anonymous로 주면 head의 요청 태그에 crossorigin 속성이 붙어 link 요청도 CORS 정책을 따르게 된다. 적용 후 캐시를 켜도 에러가 나지 않는 것을 확인했다.
- 회고: 해결책은 단순했지만 그 과정에서 preload의 CORS 정책, CommonJS와 ES Module 차이, Dynamic Import, Code Splitting과 Client-Side Navigation 동작을 함께 익혔고 Next.js 저장소에 버그 제보까지 했다고 밝힌다.
왜 읽나캐시를 켰을 때만 나는 CORS 에러처럼 조건이 이상한 버그를 만났다면, 프레임워크 빌드 산출물과 브라우저 캐시 정책까지 내려가는 추적 경로를 그대로 볼 수 있다.