pile·
프론트엔드·카카오 엔터테인먼트 FE카카오 엔터테인먼트 FE·

문제 해결을 넘어 - 문제의 근본적인 원인 찾아가기 Part 2

카카오페이지 웹에서 정주행 모드 옵션이 유지되지 않던 문제의 원인을 끝까지 파고든 글의 파트 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 에러처럼 조건이 이상한 버그를 만났다면, 프레임워크 빌드 산출물과 브라우저 캐시 정책까지 내려가는 추적 경로를 그대로 볼 수 있다.
카카오 엔터테인먼트 FE
카카오 엔터테인먼트 FE 블로그
원문은 여기서 이어서 읽을 수 있어요
원문 읽기
읽음 (0)

이 글과 비슷한

  1. 프론트엔드·여기어때 (GC컴퍼니)여기어때 (GC컴퍼니)·

    항공 프론트엔드 구축기 (7/10): 창구를 하나만 두었습니다

    여기어때 항공 서비스 프론트엔드가 웹과 앱 웹뷰 두 환경에서 동일한 함수 호출로 동작하는 앱 브릿지 추상화 레이어를 설계한 과정을 다룬다. iOS·안드로이드 규약 차이와 "웹에 존재하지 않는 브릿지를 어떻게 호출하나"라는 문제를 단일 추상화 층으로 해결한 구현 사례다.

    요약 이어보기
    #react#typescript#webview+2