
CSS-in-JS, 그 달콤했던 배신과 우리의 새로운 선택
풀링포레스트 CTO가 전하는 CSS-in-JS의 기술적 부채와 Zero-runtime 솔루션(Tailwind CSS, Vanilla Extract)으로의 전환기입니다.
송찬영
CTO

안녕하세요. 풀링포레스트 CTO 송찬영입니다.
최근 "CSS-in-JS: 프런트엔드 건전성에 대한 위대한 배신"이라는 다소 자극적인 제목의 아티클을 접했습니다. 제목을 보자마자 묘한 기시감이 들더군요. 불과 몇 년 전까지만 해도 우리는 Styled-components나 Emotion 같은 라이브러리를 보며 "이것이야말로 모던 웹 개발의 구원자"라고 외쳤으니까요. 하지만 저 또한 최근 우리 팀의 기술 스택을 재점검하며, 그 '구원자'가 남긴 기술적 부채를 뼈저리게 느끼고 있었습니다. 오늘은 우리가 왜 CSS-in-JS에 열광했었고, 지금은 왜 다른 길을 모색하고 있는지, 그 치열했던 고민의 과정을 이야기해 보려 해요.
솔직히 고백하자면, 풀링포레스트의 초기 서비스는 Styled-components로 도배되어 있었습니다. 당시 우리에게 가장 중요한 건 '속도'와 '캡슐화'였어요. 컴포넌트 단위로 스타일을 묶어두니 유지보수가 편했고, 클래스 이름이 겹칠까 봐 걱정할 필요도 없었죠. 자바스크립트 변수를 스타일에 바로 꽂아 넣을 수 있는 개발 경험(DX)은 정말 달콤했습니다. 주니어 개발자들도 금방 적응했고, 우리는 빠르게 기능을 배포하며 성장했습니다.
하지만 서비스 규모가 커지고 트래픽이 몰리기 시작하자, 그 달콤함은 독이 되어 돌아왔습니다. 문제는 성능이었습니다. 사용자가 페이지에 진입할 때마다 브라우저는 자바스크립트를 다운로드하고, 파싱하고, 실행한 뒤에야 스타일을 계산해서 주입했습니다. 이른바 '런타임 오버헤드'죠.
특히 마케팅 팀에서 요청한 랜딩 페이지의 성능 최적화 작업을 할 때 막막함을 느꼈습니다. 분명 코드는 깔끔한데, Lighthouse 점수의 TBT(Total Blocking Time)가 좀처럼 줄지 않더군요. 복잡한 인터랙션이 들어간 대시보드 화면에서는 컴포넌트가 다시 렌더링될 때마다 스타일 계산이 다시 일어나면서 미세한 버벅거림(Jank)이 발생했습니다. 개발자의 편리함을 위해 사용자의 쾌적함을 희생하고 있다는 사실을 깨달았을 때, CTO로서 등골이 서늘했습니다.
더 큰 파도는 React 생태계의 변화였습니다. React Server Components(RSC)가 등장하면서 서버 중심의 렌더링이 중요해졌는데, 런타임에 스타일을 생성하는 기존 CSS-in-JS 방식은 이 흐름과 정면으로 충돌했습니다. 서버에서 미리 렌더링을 끝내서 보내야 하는데, 클라이언트에서 자바스크립트가 돌아가야만 스타일이 입혀지는 구조는 비효율의 극치였으니까요.
결국 우리는 결단을 내려야 했습니다. "스타일링 시스템을 뜯어고치자."
하지만 무작정 과거의 방식인 Sass나 별도의 CSS 파일로 돌아가는 건 정답이 아니었습니다. 우리는 'Zero-runtime' 솔루션을 찾아야 했습니다. 런타임 부하 없이 CSS를 빌드 타임에 미리 생성해내는 도구들 말이죠. 치열한 내부 논의와 PoC(개념 증명) 끝에, 우리는 Tailwind CSS와 Vanilla Extract를 도입하기로 결정했습니다.
Tailwind CSS는 유틸리티 클래스 기반이라 번들 크기를 획기적으로 줄일 수 있었고, 무엇보다 런타임 연산이 '제로'였습니다. 디자인 시스템이 엄격하게 필요한 코어 컴포넌트에는 Vanilla Extract를 사용하여 타입 안정성(Type Safety)을 챙기면서도 빌드 시점에 정적 CSS 파일을 생성하도록 했습니다.

이 과정이 순탄치만은 않았습니다. 기존 코드를 전부 들어내는 '빅뱅' 방식 대신, 신규 기능부터 적용하는 점진적 마이그레이션 전략을 택했습니다. 개발자들은 처음에 "편한 props 넘기기를 놔두고 왜 복잡하게 가냐"며 볼멘소리를 하기도 했습니다. 하지만 배포 후 성과는 명확했습니다. 초기 로딩 속도가 눈에 띄게 빨라졌고, 모바일 기기에서의 스크롤 성능도 부드러워졌습니다. 무엇보다 Lighthouse 점수가 초록색으로 바뀌었을 때 팀원들이 느낀 성취감은 대단했습니다.
기술은 계속 변합니다. 어제의 정답이 오늘의 오답이 되기도 하죠. CSS-in-JS가 나쁘다는 것이 아닙니다. 동적인 테마 변경이 잦거나 런타임 제어가 필수적인 소규모 앱에서는 여전히 훌륭한 도구입니다. 다만, 우리가 만드는 서비스의 특성과 규모, 그리고 웹 생태계의 흐름을 고려할 때 '왜' 이 도구를 쓰는지 끊임없이 질문해야 합니다.
개발자로서 우리가 경계해야 할 것은 특정 기술에 대한 맹신, 그리고 변화에 대한 두려움입니다. 기술이 우리를 배신했다고 느끼는 순간이 온다면, 그것은 도구의 잘못이라기보다 그 도구의 유효기간과 적합성을 냉철하게 판단하지 못한 우리의 안일함 때문일지도 모릅니다.
지금 여러분의 프로젝트는 어떤가요? 개발자의 편리함이라는 명분 뒤에 사용자의 불편함을 숨겨두고 있지는 않나요? 한 번쯤 동료들과 커피 한잔하며 우리의 '스타일'에 대해 진지하게 이야기 나눠보시길 권합니다.


