Web에서 Style을 입히는 방법(Claude와 Css 공부 티키타카)
브라우저 렌더링부터 CSS 라이브러리까지
목차
- 브라우저 렌더링 기초
- SCSS - CSS 전처리기
- Tailwind CSS - 유틸리티 우선 프레임워크
- Vanilla Extract - Zero-runtime CSS-in-TypeScript
- 전체 비교 및 선택 가이드
나의 생각
- Zero Runtime에 대한 궁금증을 시작으로 스터디를 시작
- 결국 CSS 파일이 생성되는 시점에 따라서 런타임 환경이 구분되는 거 아닐까? 런타임 환경에서 css-in-js가 아니라면 성능은 같지 않을까?
- 물론 컴파일 과정이나 개발 환경에서 차이점이 분명하지만 런타임 환경에서는 다를 수 없다는 결론을 내렸다.
- Vanilla Extract가 동적 스타일 변경이나 관리를 편하게 문법적 설탕을 잘 만들었다고 생각한다.
- 하지만 Zero-runtime이라는 타이틀이 Scss나 Tailwind를 사용하는 것보다 엄청 대단한 개념이나 기술이 아니라는 점도 확실히 이해했다.
- 프론트엔드 개발자가 Webpack/vite/next 등 다양한 빌드 도구에 대해서 이해해야하는 이유도 다시 한 번 확인했다.
- 도대체 빌드는 뭐고 컴파일은 뭐고 런타임은 뭘까? 라는 의문점이 있다면 개발 단계에서 Production 환경까지의 과정 그리고 내가 정의한 스타일이 브라우저에서 어떻게 적용되는지 알아보면 해결될 것 같다.
1. 브라우저 렌더링 기초
1.1 전체 렌더링 파이프라인
브라우저가 HTML, CSS, JavaScript를 화면에 그리는 전체 과정입니다.
HTML 파일 다운로드
↓
HTML 파싱 → DOM Tree 생성
↓
CSS 파일 다운로드 (병렬)
↓
CSS 파싱 → CSSOM Tree 생성
↓
DOM + CSSOM → Render Tree 생성
↓
Layout (Reflow) - 요소의 위치와 크기 계산
↓
Paint - 실제 픽셀로 그리기
↓
Composite - 레이어 합성
↓
🎨 화면에 표시
1.2 DOM과 CSSOM
DOM (Document Object Model)
HTML을 트리 구조로 변환한 것입니다.
<html>
<body>
<div class="container">
<h1>Hello</h1>
</div>
</body>
</html>
위 HTML은 다음과 같은 DOM 트리가 됩니다:
Document
└─ html
└─ body
└─ div.container
└─ h1
└─ "Hello"
CSSOM (CSS Object Model)
CSS를 트리 구조로 변환한 것입니다.
body {
font-size: 16px;
}
.container {
padding: 20px;
}
h1 {
color: blue;
}
위 CSS는 다음과 같은 CSSOM 트리가 됩니다:
body
├─ font-size: 16px (자식들이 상속)
└─ .container
├─ padding: 20px
└─ h1
└─ color: blue
1.3 중요한 특징
CSS는 렌더링 차단 리소스
- CSS 파일이 완전히 로드되고 파싱될 때까지 화면에 아무것도 그리지 않습니다
- 이유: 스타일 없이 HTML을 먼저 보여주면 깜빡임(FOUC)이 발생하기 때문
JavaScript는 DOM과 CSSOM 모두 차단
- <script> 태그를 만나면 HTML 파싱이 멈춥니다
- JavaScript가 CSSOM에도 접근 가능하므로 CSS 로딩도 기다려야 합니다
병렬 다운로드
- 여러 CSS 파일을 동시에 다운로드할 수 있습니다
- 하지만 파싱은 순차적으로 진행됩니다
1.4 화면에 실제로 보이는 시점
Composite 단계가 완료되는 순간 화면에 픽셀이 표시됩니다.
초기 렌더링 타임라인
0ms - HTML 다운로드 시작
50ms - HTML 파싱 시작, CSS 다운로드 시작
100ms - CSS 다운로드 완료, CSSOM 생성
150ms - Render Tree 생성
200ms - Layout 계산 완료
250ms - Paint 완료
300ms - Composite 완료 → 🎨 사용자가 화면을 봄!
300ms까지는 사용자는 빈 화면을 보고 있습니다.
점진적 렌더링
- CSS는 반드시 기다립니다 (렌더링 차단)
- 이미지, 폰트는 기다리지 않습니다 (먼저 레이아웃을 잡고, 나중에 도착하면 다시 그립니다)
1.5 Layout (Reflow) 단계
각 요소가 화면의 어디에, 얼마나 크게 배치되어야 하는가를 계산하는 과정입니다.
계산 과정
- Render Tree를 위에서 아래로 순회 (부모 → 자식 순서)
- 박스 모델 계산 (Content → Padding → Border → Margin)
- 레이아웃 알고리즘 적용 (Block, Flexbox, Grid 등)
Reflow가 발생하는 경우
Reflow는 비용이 큰 작업이므로 브라우저는 가능한 한 피하려고 합니다.
항상 Reflow를 일으키는 것들
DOM 조작:
element.appendChild(newElement);
element.remove();
element.innerHTML = '...';
크기/위치 변경:
element.style.width = '500px';
element.style.height = '500px';
element.style.margin = '20px';
element.style.padding = '10px';
element.style.display = 'none';
기타:
window.resize(); // 윈도우 리사이즈
element.classList.add(); // 클래스 변경
Layout 정보를 읽을 때도 강제 Reflow
// 이런 속성들을 읽으면 최신 Layout을 즉시 계산
element.offsetWidth
element.offsetHeight
element.getBoundingClientRect()
window.getComputedStyle(element)
Reflow 최적화
❌ 나쁜 예 (여러 번 Reflow):
element.style.width = '100px'; // Reflow 1
element.style.height = '100px'; // Reflow 2
element.style.margin = '10px'; // Reflow 3
✅ 좋은 예 (한 번만 Reflow):
// 방법 1: cssText 사용
element.style.cssText = 'width: 100px; height: 100px; margin: 10px;';
// 방법 2: class 변경
element.className = 'new-style';
// 방법 3: DocumentFragment 사용
const fragment = document.createDocumentFragment();
fragment.appendChild(element1);
fragment.appendChild(element2);
document.body.appendChild(fragment); // 한 번만 Reflow
비용 비교
width 변경 → Layout + Paint + Composite 🔴 비용 큼
color 변경 → Paint + Composite 🟡 중간
transform → Composite만 🟢 비용 작음
opacity → Composite만 🟢 비용 작음
애니메이션 최적화:
/* ❌ 나쁜 예 */
.box {
transition: left 0.3s; /* Layout 발생 */
}
/* ✅ 좋은 예 */
.box {
transition: transform 0.3s; /* Composite만 */
}
2. SCSS - CSS 전처리기
2.1 핵심 개념
SCSS는 CSS의 확장 문법으로, 개발자가 SCSS로 작성하면 빌드 타임에 일반 CSS로 컴파일되어 브라우저에 전달됩니다.
전체 처리 과정
개발 단계 빌드 단계 브라우저
┌─────────┐ ┌─────────┐ ┌─────────┐
│ .scss │ 컴파일 │ .css │ 로딩 │ CSSOM │
│ 파일 │ ────→ │ 파일 │ ────→ │ 생성 │
└─────────┘ └─────────┘ └─────────┘
2.2 주요 기능
변수 (Variables)
$primary-color: #3498db;
$padding: 20px;
- 색상, 크기 등을 변수로 관리
- 재사용과 일관성 유지
중첩 (Nesting)
.card {
padding: $padding;
.card-header {
color: $primary-color;
&:hover {
color: darken($primary-color, 10%);
}
}
}
- HTML 구조처럼 CSS를 중첩해서 작성
- 선택자의 계층 구조를 명확하게 표현
믹스인 (Mixins)
@mixin flex-center {
display: flex;
justify-content: center;
align-items: center;
}
.container {
@include flex-center;
height: 100vh;
}
- 재사용 가능한 스타일 블록
- 매개변수를 받아 동적으로 생성 가능
상속 (Extend)
%button-base {
padding: 10px 20px;
border: none;
cursor: pointer;
}
.button-primary {
@extend %button-base;
background: $primary-color;
}
- 다른 선택자의 스타일을 상속
- 중복 코드 제거
2.3 컴파일 과정
Sass 컴파일러의 처리 순서
1단계: 변수 파싱
- 모든 변수 선언을 찾아서 저장
2단계: 중첩 해제
// 입력
.card {
.header {
color: blue;
}
}
// 출력
.card .header {
color: blue;
}
3단계: 믹스인 인라인화
// 입력
.container {
@include flex-center;
}
// 출력
.container {
display: flex;
justify-content: center;
align-items: center;
}
4단계: 함수 실행
// 입력
$color: #3498db;
.header {
color: darken($color, 10%);
}
// 출력
.header {
color: #2980b9;
}
5단계: 상속 처리
// 입력
.button-primary, .button-secondary {
@extend %button-base;
}
// 출력
.button-primary, .button-secondary {
padding: 10px 20px;
border: none;
}
2.4 빌드 도구 통합
Webpack
module.exports = {
module: {
rules: [
{
test: /\.scss$/,
use: [
'style-loader', // 3. JS로 CSS를 DOM에 주입
'css-loader', // 2. CSS를 JS로 변환
'sass-loader' // 1. SCSS를 CSS로 컴파일
]
}
]
}
};
Vite
// vite.config.js
export default {
css: {
preprocessorOptions: {
scss: {
additionalData: `$injectedColor: orange;`
}
}
}
};
Vite는 내장 SCSS 지원이 있어서 sass 패키지만 설치하면 자동 처리됩니다.
2.5 브라우저 전달
개발 모드
<head>
<style>
.card { padding: 20px; background: white; }
.card .card-header { color: #3498db; }
.container { display: flex; justify-content: center; }
</style>
</head>
프로덕션 모드
<head>
<link rel="stylesheet" href="/assets/styles.abc123.css">
</head>
/* styles.abc123.css - 압축됨 */
.card{padding:20px;background:#fff}.card .card-header{color:#3498db}.container{display:flex;justify-content:center}
2.6 장단점
✅ 장점
- 빌드 타임 처리: 런타임 성능 영향 제로
- 강력한 기능: 변수, 중첩, 믹스인, 함수
- 코드 재사용성: 믹스인과 상속으로 DRY 원칙
- 중첩으로 가독성 향상: HTML 구조와 유사하게 작성
❌ 단점
- 동적 스타일 불가능: 빌드 타임에 모든 값이 결정
- 전역 네임스페이스: CSS Modules나 BEM 필요
- 빌드 설정 필요: Webpack, Vite 등 설정 필요
3. Tailwind CSS - 유틸리티 우선 프레임워크
3.1 핵심 개념
Tailwind는 미리 정의된 유틸리티 클래스를 HTML에서 조합해서 스타일을 만드는 방식입니다.
전체 처리 과정
개발 단계 빌드 단계 브라우저
┌─────────┐ ┌──────────┐ ┌─────────┐
│ HTML │ 스캔 │ Tailwind │ CSS 생성 │ CSSOM │
│ 클래스 │ ────→ │ CLI │ ────────→ │ 생성 │
└─────────┘ └──────────┘ └─────────┘
SCSS와의 차이
SCSS: CSS를 확장해서 작성 → CSS 생성
Tailwind: HTML 클래스 스캔 → 사용된 클래스만 CSS 생성
3.2 사용 예시
<div class="flex items-center justify-center h-screen bg-gray-100">
<div class="max-w-md p-6 bg-white rounded-lg shadow-lg">
<h1 class="text-2xl font-bold text-blue-600 mb-4">
Hello World
</h1>
<button class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
Click me
</button>
</div>
</div>
각 클래스는 하나의 CSS 속성에 대응:
- flex → display: flex
- items-center → align-items: center
- bg-blue-500 → background-color: #3b82f6
- p-6 → padding: 1.5rem
3.3 빌드 프로세스
1. 파일 스캔
└─ content에 지정된 파일들을 읽음
2. 클래스 추출
└─ className="..." 안의 Tailwind 클래스 찾기
3. CSS 생성
└─ 사용된 클래스에 해당하는 CSS만 생성
4. 최적화
└─ 중복 제거, 압축
예시
입력 (JSX):
<div className="flex items-center bg-blue-500">
<p className="text-white font-bold">Hello</p>
</div>
추출된 클래스:
- flex, items-center, bg-blue-500, text-white, font-bold
생성된 CSS:
.flex { display: flex; }
.items-center { align-items: center; }
.bg-blue-500 { background-color: rgb(59 130 246); }
.text-white { color: rgb(255 255 255); }
.font-bold { font-weight: 700; }
3.4 JIT (Just-In-Time) 모드
Tailwind v3부터 기본 방식입니다.
기존 방식 vs JIT
기존 방식:
모든 가능한 클래스 생성 (10-50MB)
↓
사용되지 않은 클래스 제거
↓
최종 CSS (20-50KB)
JIT 방식:
파일 스캔
↓
사용된 클래스만 즉시 생성
↓
최종 CSS (20-50KB)
JIT의 장점
임의 값 사용 가능:
<div class="w-[137px]">
<div class="bg-[#1da1f2]">
즉시 생성:
.w-\[137px\] { width: 137px; }
.bg-\[\#1da1f2\] { background-color: #1da1f2; }
빠른 빌드 속도:
- 필요한 것만 생성
- 개발 중에도 즉시 반영
3.5 PostCSS 통합
PostCSS란?
PostCSS는 CSS를 변환하는 도구입니다. JavaScript 플러그인으로 CSS를 처리합니다.
입력 CSS → PostCSS → 플러그인들 → 출력 CSS
Vite와 함께
// postcss.config.js
module.exports = {
plugins: {
tailwindcss: {}, // @tailwind 디렉티브 처리
autoprefixer: {} // 브라우저 호환성
}
}
처리 흐름:
1. src/index.css 읽기
@tailwind base;
@tailwind utilities;
2. PostCSS의 Tailwind 플러그인 실행
→ 실제 CSS로 변환
3. Autoprefixer 실행
→ 벤더 프리픽스 추가
4. 최종 CSS 생성
PostCSS 없이는 Tailwind가 작동하지 않습니다.
3.6 동적 클래스명 문제
왜 동적 클래스명을 감지 못하는가?
Tailwind는 정적 분석을 사용합니다:
// ✅ 감지 가능
<div className="bg-blue-500">
// ❌ 감지 불가 - 문자열 조합
const color = 'blue'
<div className={`bg-${color}-500`}>
// ❌ 감지 불가 - 변수
const className = 'bg-blue-500'
<div className={className}>
이유:
- 빌드 타임에 JavaScript를 실행할 수 없음
- 단순 텍스트 매칭만 가능
해결 방법
1. 전체 클래스명 사용 (권장):
// ❌ 나쁜 예
<div className={`bg-${color}-500`}>
// ✅ 좋은 예
const colorClasses = {
blue: 'bg-blue-500',
red: 'bg-red-500',
green: 'bg-green-500'
}
<div className={colorClasses[color]}>
2. Safelist 사용:
// tailwind.config.js
module.exports = {
safelist: [
'bg-blue-500',
'bg-red-500',
'bg-green-500'
]
}
Safelist에 추가하면 파일에 없어도 CSS에 포함됩니다.
3.7 브라우저 전달
프로덕션 모드
<head>
<link rel="stylesheet" href="/assets/main.abc123.css">
</head>
/* 압축됨 */
.flex{display:flex}.items-center{align-items:center}.bg-blue-500{background-color:rgb(59 130 246)}
파일 크기: 20-50KB (gzip 압축 후)
3.8 장단점
✅ 장점
- 빠른 개발 속도: HTML에서 바로 스타일 작성
- 일관된 디자인 시스템: 미리 정의된 값 사용
- 작은 번들 크기: 사용한 클래스만 포함
- JIT로 무한한 유연성: 임의 값 사용 가능
❌ 단점
- HTML 가독성 저하: 클래스가 많아지면 복잡
- 학습 곡선: 모든 유틸리티 클래스 암기 필요
- 동적 스타일 제한: 문자열 조합 불가
- 커스터마이징 복잡성: 기본 디자인에서 벗어나면 복잡
4. Vanilla Extract - Zero-runtime CSS-in-TypeScript
4.1 핵심 개념
Vanilla Extract는 TypeScript로 스타일을 작성하지만, 빌드 타임에 정적 CSS로 변환되는 방식입니다.
전체 처리 과정
개발 단계 빌드 단계 브라우저
┌─────────┐ ┌──────────┐ ┌─────────┐
│ .css.ts │ 실행 │ Vanilla │ CSS 생성 │ CSSOM │
│ 파일 │ ────→ │ Extract │ ────────→ │ 생성 │
└─────────┘ └──────────┘ └─────────┘
다른 라이브러리와의 차이
styled-components: 런타임에 JS로 CSS 생성
Vanilla Extract: 빌드 타임에 TS → CSS 변환
4.2 사용 예시
스타일 정의
// Button.css.ts
import { style, styleVariants } from '@vanilla-extract/css';
export const button = style({
padding: '10px 20px',
borderRadius: '8px',
border: 'none',
cursor: 'pointer',
':hover': {
transform: 'translateY(-2px)'
}
});
export const buttonVariants = styleVariants({
primary: {
backgroundColor: '#3b82f6',
color: 'white',
},
secondary: {
backgroundColor: '#6b7280',
color: 'white',
}
});
컴포넌트에서 사용
// Button.tsx
import * as styles from './Button.css';
export function Button({ variant = 'primary', children }) {
return (
<button className={`${styles.button} ${styles.buttonVariants[variant]}`}>
{children}
</button>
);
}
4.3 주요 API
style() - 단일 스타일 생성
export const container = style({
display: 'flex',
gap: '16px'
});
styleVariants() - 여러 변형 생성
export const sizes = styleVariants({
small: { fontSize: '12px' },
medium: { fontSize: '16px' },
large: { fontSize: '20px' }
});
createTheme() - 테마 생성
export const [themeClass, vars] = createTheme({
color: {
primary: '#3b82f6',
secondary: '#6b7280'
}
});
export const button = style({
backgroundColor: vars.color.primary // 타입 안전!
});
4.4 빌드 프로세스
1. .css.ts 파일 발견
↓
2. TypeScript 코드 실행
↓
3. CSS 생성
↓
4. 클래스명 해싱
↓
5. .css.ts를 클래스명 export로 변환
↓
6. 정적 CSS 파일 생성
변환 과정
입력 (Button.css.ts):
export const button = style({
padding: '10px 20px',
backgroundColor: '#3b82f6'
});
1단계: TypeScript 실행 & 데이터 수집
{
identifier: 'button',
styles: {
padding: '10px 20px',
backgroundColor: '#3b82f6'
}
}
2단계: 클래스명 생성
Button.css.ts + button → button_abc123
3단계: CSS 생성
.button_abc123 {
padding: 10px 20px;
background-color: #3b82f6;
}
4단계: .css.ts 재작성
// 빌드 후
export const button = 'button_abc123';
5단계: 컴포넌트에서 사용
import * as styles from './Button.css';
// styles.button은 'button_abc123' 문자열
<button className={styles.button}>
4.5 상태 기반 스타일링
Vanilla Extract는 빌드 타임에 CSS가 생성되므로, 런타임 상태를 직접 사용할 수 없습니다.
방법 1: styleVariants (권장)
// Button.css.ts
export const buttonState = styleVariants({
active: {
backgroundColor: '#3b82f6',
color: 'white'
},
inactive: {
backgroundColor: '#e5e7eb',
color: '#6b7280'
},
loading: {
backgroundColor: '#93c5fd',
opacity: 0.7
}
});
// Button.tsx
<button className={`${styles.button} ${styles.buttonState[state]}`}>
Button
</button>
방법 2: CSS Variables (완전 동적 값)
// Box.css.ts
import { style, createVar } from '@vanilla-extract/css';
export const widthVar = createVar();
export const colorVar = createVar();
export const box = style({
width: widthVar,
backgroundColor: colorVar
});
// Box.tsx
import { assignInlineVars } from '@vanilla-extract/dynamic';
<div
className={styles.box}
style={assignInlineVars({
[styles.widthVar]: `${width}px`,
[styles.colorVar]: color
})}
/>
브라우저 렌더링:
<div
class="box_abc123"
style="--widthVar__xyz: 150px; --colorVar__xyz: #3b82f6"
>
</div>
핵심 원칙
"모든 가능한 스타일을 빌드 시 생성, 런타임에는 선택만"
4.6 브라우저 전달
프로덕션 모드
<head>
<link rel="stylesheet" href="/assets/styles.xyz789.css">
</head>
/* 압축됨 */
.button_abc123{padding:10px 20px;background-color:#3b82f6;color:#fff}
중요:
- TypeScript 코드는 전혀 포함되지 않음
- 런타임에 JavaScript 실행 없음
4.7 장단점
✅ 장점
- Zero Runtime: 브라우저에서 JavaScript 실행 없음
- 완전한 타입 안정성: TypeScript의 모든 기능 활용
- 스코프 격리: 자동으로 고유한 클래스명 생성
- 정적 추출: CSS가 별도 파일로 생성, 캐싱 활용
- 개발 경험: 자동완성, 리팩토링 안전
❌ 단점
- 제한적인 동적 스타일: 런타임 값을 직접 사용 불가
- 빌드 설정 필요: Vite/Webpack 플러그인 설정
- 학습 곡선: 새로운 API 학습 필요
- 디버깅 어려움: 해시된 클래스명
5. 전체 비교 및 선택 가이드
5.1 한눈에 보는 비교표
특징 SCSS Tailwind CSS Vanilla Extract
| 작성 방식 | CSS 확장 문법 | HTML 클래스 | TypeScript |
| 변환 시점 | 빌드 타임 | 빌드 타임 | 빌드 타임 |
| 런타임 비용 | 없음 | 없음 | 없음 |
| 타입 안정성 | 없음 | 없음 | 완벽 |
| 동적 스타일 | 불가능 | 제한적 (Safelist) | 제한적 (CSS Variables) |
| 번들 크기 | 중간 | 작음 (20-50KB) | 작음 (20-50KB) |
| 학습 곡선 | 낮음 | 중간 | 중간 |
| 개발 속도 | 중간 | 빠름 | 중간 |
| 유지보수 | 중간 | 쉬움 | 쉬움 |
5.2 처리 방식 비교
SCSS
개발: .scss 파일 작성
↓
빌드: CSS로 컴파일
↓
브라우저: 일반 CSS 파싱
특징:
- CSS 문법의 확장
- 변수, 중첩, 믹스인 등 강력한 기능
- 전역 네임스페이스 문제
Tailwind CSS
개발: HTML에 유틸리티 클래스 작성
↓
빌드: 파일 스캔 → 사용된 클래스만 CSS 생성
↓
브라우저: 일반 CSS 파싱
특징:
- 유틸리티 우선 접근
- HTML을 벗어나지 않고 스타일 작성
- JIT로 임의 값 사용 가능
Vanilla Extract
개발: .css.ts 파일 작성
↓
빌드: TypeScript 실행 → CSS 생성 → 클래스명 export
↓
브라우저: 일반 CSS 파싱
특징:
- TypeScript로 타입 안전한 스타일
- Zero runtime (런타임 비용 없음)
- 빌드 타임에 모든 것이 결정
5.3 동적 스타일 처리 비교
SCSS
// ❌ 불가능
.box {
width: $dynamicWidth; // 런타임 값 불가
}
// ✅ 가능: 미리 정의된 변형
.box-small { width: 100px; }
.box-large { width: 200px; }
Tailwind CSS
// ❌ 불가능
<div className={`w-${width}`}>
// ✅ 가능: 전체 클래스명
const widthClasses = {
small: 'w-24',
large: 'w-48'
}
<div className={widthClasses[size]}>
// ✅ 가능: Safelist
// tailwind.config.js
safelist: ['w-24', 'w-48']
Vanilla Extract
// ❌ 불가능
export const box = style({
width: props.width // 런타임 값 불가
});
// ✅ 가능: styleVariants
export const widthVariants = styleVariants({
small: { width: '100px' },
large: { width: '200px' }
});
// ✅ 가능: CSS Variables
export const widthVar = createVar();
export const box = style({
width: widthVar
});
// 컴포넌트에서
style={assignInlineVars({ [widthVar]: `${width}px` })}
5.4 성능 비교
초기 로딩 (First Paint)
SCSS: HTML + CSS (50KB)
Tailwind CSS: HTML + CSS (30KB)
Vanilla Extract: HTML + CSS (30KB)
모두 빌드 타임 처리로 런타임 오버헤드 없음
런타임 성능
SCSS: CSS 파싱만
Tailwind CSS: CSS 파싱만
Vanilla Extract: CSS 파싱만
차이 없음 (모두 정적 CSS)
개발 빌드 속도
SCSS: 빠름 (간단한 컴파일)
Tailwind CSS: 매우 빠름 (JIT)
Vanilla Extract: 중간 (TypeScript 실행)
6. 결론
6.1 핵심 요약
브라우저 관점
모든 방식이 결국 같은 결과:
SCSS로 작성 → CSS 변환 → 브라우저는 CSS만 봄
Tailwind로 작성 → CSS 변환 → 브라우저는 CSS만 봄
VE로 작성 → CSS 변환 → 브라우저는 CSS만 봄
브라우저는 어떤 도구를 사용했는지 전혀 모릅니다. 모두 동일한 렌더링 과정을 거칩니다:
CSS 파싱 → CSSOM → Render Tree → Layout → Paint → Composite
개발자 관점
차이는 개발 경험과 유지보수성:
- SCSS: CSS를 더 편하게 작성
- Tailwind: 빠르고 일관된 개발
- Vanilla Extract: 타입 안전과 성능 최적화
6.2 선택 결정 트리
타입 안정성이 최우선인가?
├─ Yes → Vanilla Extract
└─ No
├─ 빠른 개발이 중요한가?
│ ├─ Yes → Tailwind CSS
│ └─ No
│ ├─ 기존 CSS 지식을 활용하고 싶은가?
│ │ ├─ Yes → SCSS
│ │ └─ No → Tailwind CSS
│ └─
└─