FEInterview Prep

css · high priority

CSS-in-JS 트레이드오프 — 런타임 · 정적 · 유틸리티

Emotion · styled-components · Tailwind · Vanilla Extract · Linaria 의 실전 선택 기준

advanced 난이도4시간토스카카오네이버당근배민라인
시작 전
이해도
매우 낮음

학습 개요

탄생 배경

쉬운 설명

복잡한 개념을 실생활 비유로 설명합니다.

레스토랑의 메뉴 시스템

런타임 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 가 충돌하는 이유 한 가지를 한 문장으로 답하라.

기대 키워드

React Context 의존클라이언트 측 스타일 주입`use client` 격리

자주 하는 오해

"styled-components 가 React 19 에서 안 돌아간다" 는 부정확. 동작은 하지만 *클라이언트 컴포넌트로 격리* 가 필요해 RSC 이점이 깎인다.

Tailwind 의 *동적 클래스* 안티 패턴은 무엇이며, 회피법은?

기대 키워드

JIT정적 식별lookup mapsafelist

자주 하는 오해

`bg-${color}-500` 식 보간이 작동한다고 오해 — JIT 가 못 잡아 스타일이 사라진다.

Vanilla Extract 와 Linaria 가 같은 zero-runtime 인데도 둘 다 살아남은 이유 한 가지는?

기대 키워드

타입 안전 vs 친숙 문법TypeScript API태그드 템플릿

자주 하는 오해

"하나가 다른 하나의 상위 호환" 은 아니다 — *문법 친숙도와 타입 강제도* 의 트레이드오프가 다르다.

학습 자료