"매 build time 에 CSS 로 추출 — runtime overhead 0". 매 styled-components/emotion 의 runtime injection 의 단점 (FCP 손실, hydration cost, RSC 비호환) 매 해결. 매 vanilla-extract (Mark Dalgleish), Linaria, Panda CSS, StyleX (Meta) 매 대표주자. 매 React Server Component 시대의 사실상 표준.
매 핵심
매 runtime CSS-in-JS 의 문제
Bundle size: 매 emotion ~10KB, styled-components ~12KB minified.
FCP 손실: 매 JS 가 parse → execute → CSS inject → paint. 매 3-tier waterfall.
Hydration cost: 매 server-rendered CSS 와 client style 의 sync overhead.
RSC 비호환: 매 React Server Component 매 runtime context 없음 — 매 styled-components/emotion 매 client-only.
Re-render cost: 매 dynamic style prop 의 매 render 마다 hash + inject.
매 zero-runtime 의 해결
Build-time extraction: 매 .css.ts → .css 로 매 extract.
Type safety: 매 TypeScript 로 token, theme, variants 매 type-check.
No runtime: 매 0 KB 추가 — 매 static CSS 와 동일.
RSC 호환: 매 build-time output 이라 server/client 무관.
CSS-only features: 매 :hover, :focus, media query 매 CSS 자체 사용.
매 응용
Next.js App Router (RSC) 와 매 vanilla-extract pairing.
Design system 의 token-based theming (light/dark, brand variant).
Component library publishing (no runtime dependency).
import{css}from'@linaria/core';import{styled}from'@linaria/react';constbutton=css`
padding: 8px 16px;
background: ${({primary}) => primary ? 'blue' : 'gray'};
`;constButton=styled.button`
border-radius: 4px;
&:hover { opacity: 0.8; }
`;// 매 build-time 에 CSS 추출, dynamic prop 매 CSS variable 로 변환.
Panda CSS — atomic + tokens
// panda.config.ts
exportdefaultdefineConfig({theme:{tokens:{colors:{brand:{value:'#0066ff'}},},},});// component
import{css}from'styled-system/css';<divclassName={css({bg:'brand',p:'4',rounded:'md',_hover:{opacity: 0.8},})}>Hello</div>// 매 build-time 에 atomic class generation. Tailwind-like.
StyleX (Meta) — atomic
import*asstylexfrom'@stylexjs/stylex';conststyles=stylex.create({button:{padding:'8px 16px',backgroundColor:{default:'blue',':hover':'darkblue'},},});<button{...stylex.props(styles.button)}>Click</button>// 매 Meta Facebook/Instagram 사용. 매 atomic class deduplication.
Migration — emotion → vanilla-extract
// Before (emotion)
constButton=styled.button`
padding: ${props=>props.size==='lg'?'16px':'8px'};
`;// After (vanilla-extract recipes)
constbutton=recipe({base:{},variants:{size:{sm:{padding: 8},lg:{padding: 16}},},});constButton=({size,...rest})=><buttonclassName={button({size})}{...rest}/>;
매 결정 기준
상황
Approach
Next.js App Router (RSC)
vanilla-extract or Panda CSS.
TypeScript theme system
vanilla-extract (createThemeContract).
Tailwind-style atomic
Panda CSS or StyleX.
Existing styled-components codebase
Linaria (similar API, build-time).
Component library 배포
vanilla-extract (zero runtime dep).
Quick prototype
Tailwind (no build setup) or emotion.
기본값: vanilla-extract (Next.js + RSC), Panda CSS (atomic), Linaria (migration).
언제: Next.js App Router (RSC) 의, performance-critical site, design system 의 type-safe theming, component library 배포 의.
언제 X: 매 highly dynamic runtime style (theme switcher 매 CSS var 로 가능 — 그 외 의), 매 prototype 단계의 (Tailwind 매 빠름).
❌ 안티패턴
Runtime variable 을 build-time 으로 expect: 매 style({ color: dynamicVar }) — dynamicVar 매 build time 의 evaluable 해야. Runtime value 매 CSS variable 로.
emotion + RSC 혼합: 매 RSC 에서 emotion css prop X. 매 Server component 매 zero-runtime 만.
css.ts 의 conditional logic: 매 if (process.env.NODE_ENV) 매 build-time 에 evaluated — 매 runtime 변경 X.
Import side effect 망각: 매 vanilla-extract *.css.ts import 가 CSS 추출 trigger — tree-shake 의 영향.
Tailwind 와 zero-runtime 의 비교 오류: 매 Tailwind 도 zero-runtime — 단지 매 CSS-in-JS 가 아닌 utility-first.
🧪 검증 / 중복
Verified (vanilla-extract docs, Linaria docs, Panda CSS docs, StyleX official).
신뢰도 A.
🕓 Changelog
날짜
변경
2026-05-08
Phase 1
2026-05-10
Manual cleanup — vanilla-extract, Linaria, Panda CSS, StyleX patterns + RSC compatibility 추가