css · high priority
CSS-in-JS 트레이드오프 — 런타임 · 정적 · 유틸리티
Emotion · styled-components · Tailwind · Vanilla Extract · Linaria 의 실전 선택 기준
학습 개요
탄생 배경
쉬운 설명
복잡한 개념을 실생활 비유로 설명합니다.
“레스토랑의 메뉴 시스템”
런타임 CSS-in-JS(styled-components/Emotion) 는 *주문이 들어올 때마다 셰프가 즉석에서 메뉴를 만드는* 식당 — 자유롭지만 손님이 몰리면 느려집니다. Zero-runtime(Vanilla Extract/Linaria) 은 *메뉴를 미리 인쇄해 둔* 식당 — 즉석성은 줄지만 처리 속도가 빠르고 메뉴판이 정리되어 있습니다. Tailwind 는 *재료 카탈로그* 를 강제 — 주방마다 다른 모양의 토마토가 아니라 *공식 토마토 한 종* 만 쓰니 일관성이 보장됩니다. RSC 는 *손님이 들어오기 전에 음식을 준비해 두는* 시스템이라, 즉석 셰프 모델과 충돌합니다.
핵심 개념
런타임 CSS-in-JS
브라우저 런타임에 JS 가 스타일을 평가해 <style> 태그를 주입. props 기반 동적 스타일이 자유롭다. 대표: styled-components, Emotion. 비용: 런타임 평가 + 메인 스레드 점유 + RSC 비호환.
Zero-runtime CSS-in-JS (정적 추출)
빌드 시점에 JS 의 스타일을 *진짜 .css 파일* 로 뽑아낸다. 런타임 비용 0, 캐시 친화적, RSC 와 호환. 대표: Vanilla Extract(타입 안전, 명시적 API), Linaria(태그드 템플릿, Emotion 친숙).
유틸리티 클래스 (Tailwind)
JS 가 아니라 *클래스 이름의 조합* 으로 스타일을 표현. JIT 컴파일러가 사용된 클래스만 추출. 디자인 토큰을 config 로 강제. 대표: Tailwind, UnoCSS.
CSS Modules / Plain CSS
여전히 가장 가볍고 호환성 1순위. 동적 스타일이 약하고 토큰 강제력이 약한 대신 *모든 도구와 제일 잘 맞는다*.
| 모델 | 결정 시점 | 런타임 비용 | RSC 호환 | 동적 스타일 |
|---|---|---|---|---|
| styled-components / Emotion | 브라우저 런타임 | 있음 (해시·삽입) | 제한적 (use client) | 매우 자유 |
| Vanilla Extract | 빌드 시 (.css.ts) | 없음 | 예 (정적 .css) | 제한적 (recipe/sprinkles) |
| Linaria | 빌드 시 (Babel/SWC 변환) | 없음 | 예 | 제한적 (CSS 변수) |
| Tailwind (JIT) | 빌드 시 (PurgeCSS 후 정적 .css) | 없음 | 예 | 클래스 토글 |
| CSS Modules | 빌드 시 (locally scoped) | 없음 | 예 | 클래스 토글 |
왜 *언제 결정되는가* 가 핵심인가
런타임 결정은 표현력을, 빌드 시 결정은 성능과 RSC 호환을 줍니다. 두 축이 양쪽 끝이라 "전부 좋게" 가능한 모델은 없습니다 — 이 트레이드오프를 직시해야 선택이 가능합니다.
실무 적용
어떤 상황에서 사용하는가
5년차 styled-components 코드베이스를 가진 팀이 Next.js App Router 로 마이그레이션을 검토 중. 동시에 디자인 토큰 정합성도 약화돼 있다.
어떻게 적용하는가
(1) styled-components 자산을 *유지보수 모드* 인 점을 근거로 신규 코드는 다른 방식으로 작성하기로 합의. (2) 단기엔 styled-components 자식을 `"use client"` 경계로 격리하면서 RSC 이점은 헤더·푸터 등에서 우선 확보. (3) 신규 컴포넌트는 *Vanilla Extract* (TS 친화 + 토큰 강제) 또는 *Tailwind* (config 강제 + 풍부한 IDE 도움) 로 양자택일 — 팀 백그라운드 따라. (4) 점진 이전을 위해 codemod / 컴포넌트 단위 마이그레이션 스프린트 구성. (5) Lighthouse / web-vitals 로 LCP/INP/번들 사이즈를 *마이그레이션 전·후* 측정해 의사결정의 근거를 남긴다.
흔한 실수와 안티패턴
- styled-components 를 *전체* 한 번에 바꾸려는 빅뱅 — 회귀 위험과 일정 압박이 크다.
- Tailwind 에서 동적 색/사이즈를 `${}` 보간으로 만들고 PurgeCSS 가 못 잡아 스타일 사라짐.
- Vanilla Extract 도입 시 빌드 플러그인 호환 (vite/turbopack) 검증 누락.
- 런타임 CSS-in-JS 와 RSC 를 동시에 두면서 `"use client"` 경계가 부풀어 RSC 이점을 잃음.
- "새 라이브러리 도입" 에 매몰되어 *디자인 토큰·컴포넌트 라이브러리* 같은 더 본질적인 문제를 미룸.
흔한 오해
"CSS-in-JS 는 다 같은 카테고리다."
교정런타임/정적 추출/유틸리티 클래스 세 모델이 섞여 있다. 트레이드오프가 다르다.
왜 중요styled-components 와 Vanilla Extract 는 같은 카테고리로 보면 비교가 안 된다 — 한쪽은 런타임, 한쪽은 빌드 시 정적 .css 다.
"Tailwind 는 클래스 폭발 때문에 큰 프로젝트에 부적합."
교정컴포넌트 라이브러리(shadcn/ui 등) 와 결합하면 큰 팀에서도 *디자인 시스템 강제* 효과로 더 적합해진다.
왜 중요클래스가 더러운 부분은 컴포넌트로 캡슐화. config 로 토큰을 *강제* 한다는 점이 큰 팀의 일관성에 결정적이다.
"styled-components 가 유지보수 모드라도 그대로 쓰면 된다."
교정신규 프로젝트엔 비추천. 기존은 유지하되 *새 코드는 다른 도구* 로 작성하는 점진 이전이 안전.
왜 중요보안 패치만 받는 라이브러리에 종속되면 향후 React/Next 업그레이드 시 호환성 부담이 커진다.
면접 질문
답변 방향 힌트
*결정 시점* 을 축으로.
반드시 언급할 키워드
- styled-components/Emotion: 런타임 평가 + `<style>` 주입
- Vanilla Extract/Linaria: 빌드 시 .css 추출 (zero-runtime)
- Tailwind: 빌드 시 사용된 클래스만 추출, 유틸리티 클래스 모델
- 런타임 비용 / RSC 호환 / 디자인 토큰 강제 측면 차이
- styled-components 는 2024.1 부터 유지보수 모드
예상 꼬리 질문
- styled-components 가 유지보수 모드로 간 이유와, 이미 도입된 코드는 어떻게 다루시겠습니까?
- Vanilla Extract 의 `recipe`/`sprinkles` 가 어떤 문제를 푸는지 설명해 주세요.
자기 점검
런타임 CSS-in-JS 와 RSC 가 충돌하는 이유 한 가지를 한 문장으로 답하라.
기대 키워드
자주 하는 오해
"styled-components 가 React 19 에서 안 돌아간다" 는 부정확. 동작은 하지만 *클라이언트 컴포넌트로 격리* 가 필요해 RSC 이점이 깎인다.
Tailwind 의 *동적 클래스* 안티 패턴은 무엇이며, 회피법은?
기대 키워드
자주 하는 오해
`bg-${color}-500` 식 보간이 작동한다고 오해 — JIT 가 못 잡아 스타일이 사라진다.
Vanilla Extract 와 Linaria 가 같은 zero-runtime 인데도 둘 다 살아남은 이유 한 가지는?
기대 키워드
자주 하는 오해
"하나가 다른 하나의 상위 호환" 은 아니다 — *문법 친숙도와 타입 강제도* 의 트레이드오프가 다르다.
학습 자료
- Vanilla ExtractTypeScript-first zero-runtime CSS-in-JS. recipe/sprinkles/dynamic 등의 공식 가이드.Docvanilla-extract.style
- Linaria — Zero-runtime CSS in JSBabel/SWC 변환으로 빌드 시 .css 추출. styled-components 친숙 문법.Doclinaria.dev
- Tailwind CSS — JIT engine유틸리티 우선 철학과 JIT 컴파일러의 동작.Doctailwindcss.com
- styled-components — Maintenance mode announcement2024.1 의 유지보수 모드 전환 공지.Blogstyled-components · 2024
- The CSS-in-React LandscapeCSS-in-JS 생태계 전반(런타임/정적/유틸리티) 을 비교한 큰 그림 글.BlogCSS-Tricks
- Comparing the top zero-runtime CSS-in-JS librariesVanilla Extract / Linaria / Astroturf 등 주요 zero-runtime 라이브러리 비교.BlogLogRocket