C.W.K.
Stream
Lesson 05 of 07 · published

Tailwind anti-pattern — 조용히 깨지는 것들

~11 min · anti-patterns, tailwind, scanning

Level 0React 입문자
0 XP0/54 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
코드 리뷰가 잡지 못하는 버그. 각각 한 설정에선 컴파일되고 돌고 괜찮아 보임 — 그러다 뭔가 바뀌는 순간 fail.

동적 클래스 이름

className={`bg-${color}-500`} 가 자연스럽게 읽히지만 Tailwind 의 정적 스캐너 깸. 스캐너가 소스에서 literal 클래스 문자열 찾음; bg-red-500 이 template literal 로 있으면 거기 안 있어서 CSS 생성 안 됨.

고침: map 써. const colors = { red: 'bg-red-500', blue: 'bg-blue-500' } as const; className={colors[color]}. Literal 클래스가 소스에 있고, 스캐너가 찾고, 런타임 인덱싱이 올바른 거 생성.

임의값 어디나

Tailwind 가 one-off hex 값에 bg-[#ff8fbe] 지원. 편함. 다만 모든 임의값이 본인 테마 우회. bg-[#ff8fbe] 가 세 파일에 있으면 진실의 원천 중복 셋 만든 거. 브랜드 색 리브랜드가 둘 놓침.

고침: 값이 두 번 나타나면 @theme 에 추가. 임의값은 진짜 one-off 용.

!important 로 cascade 우회

Tailwind 가 !important 추가하는 ! prefix 출하: !text-red-500. 다른 방식으로 안 닿는 진짜 외부 CSS (서드파티 widget override) 에 사용. 본인 컴포넌트 이기려고 쓰지 마 — 컴포넌트 CSS 가 잘못된 레이어 (lesson 3) 에 있다는 뜻.

컴포넌트 로직보다 긴 클래스 문자열

유틸리티 클래스 20개 가진 버튼이 5개 가진 거보다 읽기 어려움. className 빽빽해지면 세 옵션:

  1. @layer components 에 커스텀 컴포넌트 클래스 추출 (lesson 3).
  2. 관련 유틸리티를 이름 붙은 상수로 그룹: const baseButton = "inline-flex rounded-lg font-medium".
  3. 구조화된 배열 + 조건과 함께 cn() 사용 (lesson 4).

20-class 줄을 'Tailwind 가 원래 이래' 라고 받아들이지 마. 가독성 포기.

CSS-in-JS 로 손 뻗기

Tailwind 프로젝트에 styled-componentsemotion 추가하고 싶으면 멈춰. 두 시스템이 서로 중복 + 번들 무게 추가. All-Tailwind (유틸 + 복잡한 케이스에 @layer components) 또는 all-CSS-in-JS — 둘 다 아님.

정적 스캐너는 친구, 적 아냐. Tailwind 의 빌드 시 스캔이 프로덕션 CSS 작게 유지 (사용 유틸리티만 출하) 하는 동력. 스캐너 좌절시키는 패턴 (동적 문자열, 런타임 클래스 합성) 은 부풀린 CSS 출하 또는 더 흔하게는 프로덕션에서 조용히 fail 하는 빠진 CSS 출하.

Code

동적 클래스 고침·tsx
// ANTI-PATTERN — 스캐너가 못 잡음
function Badge({ color }: { color: "red" | "green" | "blue" }) {
  return <span className={`bg-${color}-500 text-white`}>!</span>;
}

// FIX 1 — 명시적 map (타입 포함)
const badgeColors: Record<"red" | "green" | "blue", string> = {
  red: "bg-red-500",
  green: "bg-green-500",
  blue: "bg-blue-500",
};
function BadgeFixed({ color }: { color: keyof typeof badgeColors }) {
  return <span className={`${badgeColors[color]} text-white`}>!</span>;
}

// FIX 2 — 임의 데이터에서 진짜로 런타임 클래스 이름 필요하면 Tailwind safelist
// (vite.config 또는 @theme 에). 최후의 수단; 보통 map 이 더 나음.
임의값 → 테마 토큰 승격·css
/* BEFORE — 같은 hex 가 세 파일에 */
/* <header className="bg-[#ff8fbe]"> ... </header> */
/* <footer className="text-[#ff8fbe]"> ... </footer> */
/* <ring className="ring-[#ff8fbe]"> ... </ring> */

/* AFTER — @theme 의 한 진실의 원천 */
@theme {
  --color-brand: #ff8fbe;
}
/* <header className="bg-brand"> ... </header> */
/* <footer className="text-brand"> ... </footer> */
/* <ring className="ring-brand"> ... </ring> */

External links

Exercise

Bootstrap 프로젝트에 anti-pattern 둘 일부러 도입: bg-${color}-500 동적 클래스, 세 곳에 쓴 bg-[#ff8fbe] 임의값. 동적 클래스가 스타일 잡혀 렌더 안 되는지 확인. 둘 다 고침 — 동적 클래스는 map 으로, 임의값은 @theme 토큰으로. 둘 다 올바르게 렌더되는지 확인.
Hint
Map 고침 후 프로덕션 빌드 (npm run build && ls dist/assets/) 확인하고 CSS 에서 bg-red-500 grep. 있어야 함. 동적 클래스로는 없었을 거.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.