카카오페이지 웹의 정주행 모드 옵션이 화 이동 시 초기화되던 문제를 파고든 글의 파트 1이다. 주먹구구식으로 고쳐 증상을 없앤 뒤, 그 해결책이 왜 통했는지 모르는 상태를 문제 삼아 근본 원인을 되짚는다. 첫 번째 문제인 기존 코드가 왜 복잡했는가를 ES Module과 Next.js Dynamic Import까지 내려가 규명한다.
핵심 포인트- 증상은 옵션 초기화였지만 그때마다 정체 모를 CORS 에러가 함께 났고, 브라우저 캐시를 끄면 둘 다 사라졌다.
- 원인 후보였던 preload는 아니었다. MDN 기준 preload에서 CORS를 따로 봐야 하는 것은 font와 fetch 타입뿐이다.
- 컴포넌트 안에서 조건부 require로 CSS를 가져오던 코드를 전역 import로 바꾸자 증상이 사라졌다.
- 그 코드가 복잡했던 이유는 같은 파일에서 swiper/react를 조건부로 가져오는 방식과 일관성을 맞추기 위해서였다.
- swiper/react는 ES Module만 지원하는데 SSR 환경의 Node.js는 CommonJS만 지원해 import가 require로 바뀌며 에러가 났다.
- Dynamic Import로 바꾸면 에러는 사라지지만 Next.js가 래핑하며 displayName이 사라져 라이브러리 기능이 깨진다.
상세 정리- 글의 문제의식: 검색이나 AI, 주먹구구로 증상은 없앨 수 있지만 왜 발생했고 그 해결책이 어떤 원리로 통했는지는 모른 채 넘어가는 경우가 많다는 것이다.
- 증상: 정주행 모드를 켠 상태에서 이전 화나 다음 화로 이동하면 옵션이 기본값으로 돌아갔다. 상태는 redux로 관리했지만 관련 코드에서 원인을 찾을 수 없었다.
- 상황 분석: 옵션이 유지되지 않을 때만 CORS 에러가 났고, 개발자도구에서 캐시 사용을 끄거나 로컬 서버로 띄우면 둘 다 발생하지 않았다.
- 대상 확인: 에러가 나는 CSS는 자체 CDN에서 가져오는 파일이었고 내용을 보니 뷰어의 넘김 애니메이션에 쓰는 swiper 라이브러리의 스타일시트였다.
- 검색 실패: 라이브러리 이름을 붙인 검색은 결과가 없었고, 일반적인 키워드로는 CSS 안에서 외부 폰트를 가져올 때의 사례만 나왔다. 문제의 파일은 폰트를 base64 데이터 URI로 넣고 있어 상황이 달랐다.
- 후보 1 preload: 헤더의 link 태그에 preload가 걸려 있어 별도 CORS 정책이 적용되는 것 아닌가 의심했지만, 태그를 지워도 에러가 계속됐다.
- 후보 1 정리: 문서상 preload에서 CORS를 별도로 고려해야 하는 대상은 font와 fetch이며 해당 파일은 style 타입이라 해당되지 않았다.
- 후보 2 가져오는 방식: window가 undefined가 아닐 때만 require로 CSS를 가져오는 형태였다. 서버 환경을 제외하려는 처리인데 CSS와 서버가 무슨 관련인지 알 수 없었다.
- 후보 2 조치: 단순 import로 바꾸고 위치도 다른 CSS들과 같은 전역 진입점으로 옮기자 CORS 에러가 사라지고 옵션도 유지됐다.
- 남은 문제: 증상은 사라졌지만 왜 그 코드가 CORS를 유발했는지, 왜 이 변경이 통했는지는 설명되지 않았다. 저자는 이를 운이 좋았을 뿐이라고 표현한다.
- 첫 질문: 그렇다면 기존 코드는 왜 그렇게 복잡했나. 주변 코드를 보니 바로 아래에서 swiper/react 모듈도 같은 조건부 require로 가져오고 있었다.
- 확인: swiper/react는 단순 import로 바꾸면 에러가 나는 반면 CSS는 나지 않았다. 즉 CSS 쪽은 모듈 쪽과 형태를 맞춘 것뿐이었다.
- 에러 메시지: ES Module을 require로 가져올 수 없으니 동적 import로 바꾸라는 내용이었다. import를 썼는데 require가 등장하는 점이 단서였다.
- 원인: 프로젝트가 SSR을 쓰므로 코드가 Node.js 서버에서도 실행되는데, 그 환경은 CommonJS만 지원해 빌드 결과에서 import가 require로 바뀐다. 그런데 이 모듈은 ES Module만 지원한다.
- 기존 처리의 이유: 그래서 서버 환경에서는 아예 가져오지 않도록 조건을 걸어 require 시도 자체를 막은 것이었다.
- 개선 시도: Next.js의 Dynamic Import에 ssr false 옵션을 주면 같은 효과를 얻으면서 require를 쓰지 않아 코드가 통일된다.
- 새 문제: 그렇게 바꾸자 에러는 사라졌지만 이미지가 좌우로 넘어가지 않고 세로로 늘어졌다. 부모 요소 안에 있어야 할 슬라이드 요소들이 같은 레벨에 놓였다.
- 소스 추적: 라이브러리를 클론해 children 관련 코드를 보니, 자식의 displayName에 특정 문자열이 있는지로 슬라이드를 골라내는 함수가 있었다.
- 확인: Dynamic Import로 가져온 컴포넌트의 displayName을 찍어보니 undefined였다. Next.js가 Loadable Component로 한 번 감싸면서 원본 이름이 사라진 것이다.
- 조치: 가져온 컴포넌트에 displayName을 직접 붙이자 기능이 정상 동작했고, 저자는 이를 버그로 보고 Next.js 저장소에 리포트했다.
- 최종 판단: displayName을 손으로 붙여야 하는 제약이 붙으면 오히려 히스토리가 늘어난다고 보고, CSS만 import로 바꾸고 모듈 쪽은 기존 구조를 유지하기로 했다.
- 또 다른 선택지: Next.js 13.1의 transpilePackages로 해당 라이브러리와 내부 의존 두 개를 트랜스파일하면 코드상으로는 가장 깔끔하게 평범한 import를 쓸 수 있다.
왜 읽나고치긴 했는데 왜 고쳐졌는지 모르는 상태가 찜찜하다면, 그 상태에서 한 단계씩 더 내려가는 추적 과정을 통째로 따라갈 수 있다.