
Tailwind CSS 프레임워크 - 유틸리티 퍼스트(Utility-First) 혁명, CSS 파일을 삭제하다
지난 수십 년 동안 웹 개발자들은 암묵적인 절대 규칙(국룰) 하나를 목숨처럼 지켜왔습니다. "HTML은 문서의 뼈대만 짜고, CSS 파일은 철저하게 분리(Separation of Concerns)해서 디자인을 따로 입혀라." 이 원칙 때문에 우리는 HTML 요소에 class="card-profile" 같은 의미론적(Semantic)인 예쁜 이름을 지어주고, 다시 style.css 파일을 열어 .card-profile { border-radius: 8px; box-shadow: ... }라고 길고 지루한 스타일을 앵무새처럼 반복해서 타이핑해야 했습니다. 파일 두 개를 번갈아 열어가며 코딩하는 피로감, 방금 지은 클래스 이름이 다른 곳과 충돌할까 봐 벌벌 떠는 BEM 네이밍의 공포 속에서 프론트엔드 개발자들은 서서히 미쳐가고 있었습니다.
이때 아담 웨던(Adam Wathan)이라는 구세주가 혜성처럼 등장해 전 세계를 향해 충격적인 선언을 던집니다. "의미론적 클래스 네이밍은 모두 헛소리다. CSS 파일을 아예 만들지 마라! HTML 태그 안에 '글씨 빨갛게', '패딩 4만큼', '모서리 둥글게'라는 기성품(Utility) 클래스 이름만 몽땅 때려 박아라! 그러면 디자인이 완성될 것이다!" 이 미친 이단아 같은 패러다임 '유틸리티 퍼스트 (Utility-First)'를 등에 업고 탄생한 괴물 프레임워크가 바로 현재 전 세계 프론트엔드 웹 생태계를 완전히 집어삼키고 독재자로 군림한 Tailwind CSS (테일윈드)입니다.
이번 글에서는 테일윈드가 20년 묵은 CSS 아키텍처를 어떻게 산산조각 냈는지, 그리고 파일 크기가 수십 메가바이트(MB)로 폭발할 뻔한 위기를 JIT(Just-in-Time) 컴파일러로 어떻게 극복했는지 다뤄보겠습니다.
1. 인라인(Inline) 스타일의 부활? 절대 아닙니다!
테일윈드의 코드를 처음 본 시니어 개발자들은 뒷목을 잡고 쓰러집니다. <button class="bg-blue-500 text-white font-bold py-2 px-4 rounded"> HTML 태그 하나에 클래스가 6~7개씩 주렁주렁 매달려 있기 때문입니다. "이건 90년대에 쓰던 style="background: blue; padding:..." 인라인 스타일 최악의 하드코딩과 다를 게 없잖아!"라고 비난합니다.
하지만 테일윈드는 인라인 스타일과 차원이 다른 3가지의 치명적인 강력함을 무기로 가집니다.
- 제약(Constraint) 기반 디자인 시스템: 인라인 스타일로
padding: 13px,color: #f2a1a1을 난사하면 사이트 전체의 여백과 색상 통일성이 붕괴됩니다. 테일윈드는 사전에 철저히 계산된 황금 비율의p-4(16px),p-5(20px)와 글로벌 컬러 팔레트blue-500만 제공합니다. 개발자가 아무리 디자인 감각이 썩었어도, 제공된 유틸리티 블록만 조립하면 무조건 '애플이나 구글 뺨치는 실리콘밸리급 깔끔한 UI'가 강제로 탄생합니다. - 반응형(Media Query) 통제: 인라인 스타일에서는 절대 미디어 쿼리를 쓸 수 없습니다. 하지만 테일윈드는
md:flex(태블릿 이상일 때만 Flex 적용),lg:text-xl(PC 창일 때만 글씨를 크게)처럼 클래스 이름 앞에md:접두사만 붙이면 기가 막히게 반응형이 구현됩니다. - 상태(Hover/Focus) 제어: 인라인으로는 절대 못하는
hover:bg-blue-700(마우스 올리면 파란색 진하게) 처리 역시 접두사 하나로 끝납니다.
2. JIT (Just-In-Time) 컴파일러의 기적: 0바이트의 마술
세상에 존재하는 모든 여백(1px부터 1000px까지), 모든 색상 팔레트, 모든 마진 값을 유틸리티 클래스로 미리 다 만들어 놓으면 그 tailwind.css 파일의 용량은 수십 기가바이트(GB)가 넘어가서 브라우저가 터져버릴 것입니다. 초창기 버전의 테일윈드는 이 무식한 용량 문제(Purge CSS 처리) 때문에 세팅이 지옥 같았습니다.
하지만 JIT(Just-in-Time, 실시간) 컴파일러 엔진이 도입되면서 판도가 180도 바뀌었습니다. 개발자가 HTML에 text-red-500이라고 타이핑하고 저장 버튼을 누르는 순간! 백그라운드에 켜져 있던 JIT 엔진이 당신이 타이핑한 그 글자만을 귀신같이 스캐닝(Scanning)하여, 0.01초 만에 .text-red-500 { color: #ef4444; }라는 단 한 줄의 진짜 CSS 코드를 메모리 상에 동적으로 찍어(Generate)냅니다. 수만 가지의 클래스 사전을 쥐고 대기하지만, 실제 최종 빌드되어 브라우저에 전송되는 CSS 파일에는 '당신이 HTML에서 실제로 한 번이라도 사용한 클래스' 딱 10kb 언저리의 깃털 같은 코드만 쏙 뽑혀서 배포됩니다. 성능 최적화의 극치입니다.
3. 더러운 HTML의 딜레마와 React(컴포넌트)의 결합
테일윈드를 쓴 코드는 솔직히 끔찍하게 못생겼습니다. 버튼 하나 만들려는데 클래스 이름이 20개가 넘어가서 태그가 화면 밖으로 튀어나갑니다(Ugly HTML). 순수 HTML/CSS 환경에서 테일윈드를 쓰면 가독성 때문에 코딩을 때려치우고 싶어질 것입니다.
하지만 React, Vue, Svelte 같은 모던 자바스크립트 프레임워크를 만나는 순간, 이 단점은 완벽한 시너지로 둔갑합니다. 리액트에서는 어차피 UI를 작고 귀여운 <Button /> 이라는 독립적인 자바스크립트 컴포넌트로 잘게 쪼개서 조립합니다. 더러운 20줄짜리 테일윈드 클래스 뭉치는 그 <Button /> 컴포넌트 뱃속에 딱 한 번만 숨겨두면 끝입니다! 바깥에서는 style.css 파일을 만들 필요도 없고, 클래스 이름(Naming)을 고민할 필요도 없이 완벽하게 독립된 UI 블록을 무한정 복제할 수 있습니다. '테일윈드 + 리액트' 조합이 현재 웹 개발 시장의 표준(Standard) 스택으로 굳어진 결정적 이유입니다.
정리하며
이번 글에서는 20년 묵은 시맨틱(Semantic) CSS 철학의 뒤통수를 갈기고 웹 생태계를 유틸리티 퍼스트로 통일해 버린 지배자, Tailwind CSS의 파괴력과 JIT 컴파일러의 기적을 상세히 살펴보았습니다. 디자인 파일을 열어볼 틈도 없이 키보드 타이핑만으로 풀스택 UI를 찍어내는 쾌감은 한 번 맛보면 절대 옛날로 돌아갈 수 없게 만듭니다. 자, 테일윈드가 '클래스 이름'으로 꼼수를 써서 CSS 파일의 독립성을 깼다면, 이보다 더 극단적으로 "아예 자바스크립트 변수 안에다가 진짜 CSS 코드를 통째로 박아버리면 안 되나?"라는 미친 발상에서 탄생한 React 생태계의 돌연변이들이 있습니다. CSS-in-JS의 시대! 다음이번 포스팅에서는 "CSS-in-JS와 Styled-Components: 자바스크립트와 한 몸이 되어버린 스타일링의 종착역"에 대해 상세히 자세히 알아보겠습니다.
CSS-in-JS (Styled-Components) - 자바스크립트와 한 몸이 되어 캡슐화의 끝판왕이 되다
이전 포스팅에서 다룬 Tailwind CSS가 HTML 태그 뱃속에 유틸리티 클래스를 구겨 넣어 CSS 파일을 없애버린 이단아였다면, 이번에 다룰 패러다임은 한술 더 뜹니다. 2015년 이후 React(리액트)와 Vue.js가 웹 생태계를 점령하면서, 프론트엔드 개발자들은 UI를 화면 단위가 아니라 작은 레고 블록(컴포넌트) 단위로 쪼개어 조립하는 '컴포넌트 주도 개발(CDD)'에 심취했습니다. 자바스크립트(로직)와 HTML(구조)은 이미 JSX라는 이름으로 한 몸(한 파일)이 되어 끈적하게 결합했는데, 어째서 CSS(디자인) 녀석만 여전히 style.css라는 외딴 섬(다른 파일)에 홀로 떨어져 있어야 할까요? "이럴 거면 아예 자바스크립트(JS) 변수 안에다가 통째로 CSS 코드를 문자열로 때려 박고, 컴포넌트가 태어날 때마다 브라우저에 CSS를 실시간으로 주사기 꽂듯 주입(Inject)해 버리자!" 이 미친 발상에서 탄생한 돌연변이 기술이 바로 'CSS-in-JS'이며, 그 거대한 패러다임의 제왕이 'Styled-Components'입니다. 글로벌 스코프 충돌이라는 CSS의 20년 묵은 원죄를 기계적으로 완벽히 박살 내버린 이 기술! 이번 글에서는 자바스크립트 백틱(``) 안에 CSS를 봉인하는 Styled-components의 렌더링 원리와, React의 Props 상태 데이터를 실시간으로 CSS 엔진에 쑤셔 넣는 동적 스타일링의 극의(極意)에 대해 상세히 알아보겠습니다.
1. CSS 파일의 장례식: 템플릿 리터럴(Template Literal)의 마법
Styled-components는 .css 파일을 아예 생성조차 하지 않습니다. React 컴포넌트 .js 파일 안에서, 자바스크립트 최신 문법인 '백틱(``)' 기호를 열고 그 안에 평범한 CSS 코드를 곧바로 작성해 버립니다.
// 자바스크립트 파일 내부입니다!
import styled from 'styled-components';
// 💡 놀랍게도 이것은 CSS가 아니라 자바스크립트 '변수' 선언입니다!
const SubmitButton = styled.button`
background-color: blue;
color: white;
padding: 10px 20px;
border-radius: 8px;
/* SCSS처럼 자기 자신 호버(&) 중첩도 기본 지원! */
&:hover {
background-color: darkblue;
}
`;
// 화면에 그릴 때는 리액트 컴포넌트처럼 태그로 사용!
function App() {
return <SubmitButton>전송하기</SubmitButton>
}
개발자는 .btn 이니 .btn--primary 이니 하는 CSS 클래스 이름 짓기(Naming)의 고통에서 완전히 해방됩니다. 그저 직관적인 자바스크립트 변수 이름(SubmitButton)만 지어주면 됩니다.
2. 렌더링 엔진의 해킹: 기괴한 해시(Hash) 클래스의 탄생
그렇다면 브라우저는 자바스크립트 변수 안에 갇힌 저 CSS 코드를 어떻게 읽어들일까요? 사용자가 웹페이지에 접속하여 React 앱이 렌더링 되는 그 0.1초의 순간, Styled-components 라이브러리가 백그라운드에서 미친 듯이 연산을 시작합니다.
라이브러리는 우리가 짠 파란색 배경 코드를 낚아채서 브라우저 HTML의 <head> 태그 안에 <style> 태그를 몰래 주입(Inject)합니다. 이때 가장 소름 돋는 마법이 펼쳐집니다. 클래스 이름을 개발자가 짓지 않고, 라이브러리가 .sc-dkzDqf, .sc-hKwDye 같은 알파벳과 숫자가 무작위로 섞인 '해시(Hash) 문자열'을 암호처럼 자동 생성하여 박아버립니다.
이 기괴한 무작위 알파벳 클래스는 전 세계 어디에도 겹치는 이름이 없습니다. 따라서 당신이 SubmitButton 컴포넌트를 수백 개를 복사 붙여넣기 하거나 다른 프로젝트에서 가져오더라도, 다른 엉뚱한 CSS 코드와 겹쳐서 화면이 깨지는 '전역 충돌(Global Collision)' 버그가 수학적으로 0% 원천 차단됩니다. OOCSS, BEM 방법론의 노가다를 기계가 완벽하게 대체해 버린 캡슐화의 끝판왕입니다.
3. CSS와 JS의 완전한 융합: Props 기반 동적 스타일링
CSS-in-JS가 테일윈드나 SCSS를 압살 하는 최고의 필살기는 바로 자바스크립트의 '변수(상태)' 데이터를 CSS 뱃속으로 실시간으로 쑤셔 넣을 수 있다는 점입니다.
사용자의 클릭이나 서버에서 받아온 데이터(Props)에 따라 버튼 색상을 빨간색, 파란색, 초록색으로 동적으로 바꿔야 한다고 가정해 봅시다. 기존 CSS 환경에서는 .btn-red, .btn-blue 클래스들을 일일이 만들어놓고 자바스크립트 if문으로 클래스를 통째로 갈아 끼워야 했습니다.
// 💡 자바스크립트 삼항 연산자를 CSS 안에서 냅다 실행해 버립니다!
const DynamicButton = styled.button`
// 부모로부터 'primary'라는 꼬리표(Props)를 받으면 파란색, 아니면 회색으로 렌더링하라!
background-color: ${props => props.primary ? "blue" : "gray"};
// 테두리 두께도 외부 자바스크립트 변수값으로 실시간 조작 가능!
border: ${props => props.thickness}px solid black;
`;
// 사용부: 꼬리표(Props)만 달아주면 다르게 렌더링 됩니다.
<DynamicButton primary thickness={5}>나는 두껍고 파란 버튼</DynamicButton>
<DynamicButton thickness={1}>나는 얇고 회색 버튼</DynamicButton>
복잡한 데이터 시각화 차트(Chart) 바의 높이나 프로그래스 바(Progress Bar)의 % 게이지를 그릴 때, 이 동적 스타일링 기법은 상상을 초월하는 개발 속도와 코드의 간결함을 선사합니다.
정리하며
이번 글에서는 CSS 코드를 자바스크립트 템플릿 리터럴로 집어삼키고 무작위 해시 클래스로 캡슐화의 정점을 찍은 Styled-components의 렌더링 마법을 상세히 살펴보았습니다. 테일윈드가 html 중심의 폭발적 생산성이라면, CSS-in-JS는 js 중심의 완벽한 상태 제어와 격리를 상징하며 모던 웹 생태계의 양대 산맥을 이루고 있습니다. (※물론 브라우저 런타임에 렌더링 부하를 일으킨다는 단점 때문에 최근에는 Vanilla Extract 같은 Zero-runtime 솔루션으로 진화 중입니다.) 자, 이렇게 우리는 CSS 기초부터 최신 트렌드 프레임워크까지 프론트엔드 스타일링의 모든 테크 트리를 정복했습니다. 이제 이 시리즈를 마무리하는 대단원의 막이 오릅니다. 아직 다루지 않은 부트스트랩, 모듈화 전략, 그리고 이 블로그 시리즈의 궁극적 목표! 다음
CSS 프레임워크의 조상 Bootstrap - 12-Grid 시스템과 덮어쓰기 지옥의 레거시
우리가 이전 포스팅들에서 열광했던 Tailwind CSS나 Styled-components가 등장하기 훨씬 이전, 웹 프론트엔드 생태계를 장장 10년 가까이 완벽하게 지배했던 전설적인 절대 군주가 있었습니다. 바로 2011년 트위터(Twitter) 개발자들이 오픈소스로 공개한 Bootstrap (부트스트랩)입니다. 당시 웹 개발자들은 float을 사용해 레이아웃을 짜느라 매일 밤을 새웠고, IE(인터넷 익스플로러) 6, 7, 8 브라우저 파편화 버그를 잡다가 멘탈이 부서져 나갔습니다. 이때 부트스트랩이 나타나 "클래스 이름에 col-md-6만 적으면 화면을 정확히 반으로 쪼개주고, btn btn-primary만 적으면 그라데이션이 들어간 화려하고 둥근 파란색 버튼을 그려주겠다!"라고 선언했습니다. 반응형 12-그리드(Grid) 시스템이라는 위대한 패러다임을 웹 생태계에 정착시킨 혁명적인 프레임워크입니다. 하지만 현재 부트스트랩은 모던 웹 진영에서 점차 '무겁고 구식인 레거시(Legacy)' 취급을 받으며 뒤안길로 물러나고 있습니다. 왜 그렇게 위대했던 기술이 밀려났을까요? 이번 글에서는 부트스트랩이 프론트엔드 역사에 남긴 위대한 유산인 12-그리드 시스템의 설계 원리와, "모든 사이트가 다 똑같이 생겼어"라는 비아냥을 듣게 만든 컴포넌트 프레임워크의 한계, 그리고 !important 덮어쓰기 지옥을 상세히 알아보겠습니다.
1. 위대한 유산: 12-Column Grid System
부트스트랩이 남긴 가장 위대한 업적은 CSS Grid나 Flexbox가 존재하지도 않던 시절에, float과 % 퍼센트 수학 계산을 이용해 화면을 12개의 세로 칸(Column)으로 쪼개는 레이아웃 시스템을 표준화시킨 것입니다.
10진법이 아니라 왜 하필 12칸일까요? 12는 1, 2, 3, 4, 6으로 나누어떨어지는 마법의 숫자입니다. 화면을 2등분(6칸씩), 3등분(4칸씩), 4등분(3칸씩) 하기가 너무나 수월하기 때문입니다.
<!-- 부트스트랩의 고전적인 12-그리드 마크업 -->
<div class="container">
<div class="row">
/* PC(md)에서는 4칸(1/3 덩치)을 차지하지만, 폰(sm)에서는 12칸(전체 덩치)을 다 차지해라! */
<div class="col-sm-12 col-md-4">왼쪽 콘텐츠</div>
<div class="col-sm-12 col-md-4">가운데 콘텐츠</div>
<div class="col-sm-12 col-md-4">오른쪽 콘텐츠</div>
</div>
</div>
이 container - row - col로 이어지는 3단 구조는 프론트엔드 퍼블리싱의 절대 공식이 되었으며, 나중에 등장한 CSS Grid 모듈이 fr 단위를 설계할 때에도 막대한 영감을 주었습니다.
2. 양날의 검: 컴포넌트 프레임워크의 저주
Tailwind가 색상, 패딩 같은 원자(Atom) 단위의 '유틸리티'를 제공한다면, Bootstrap은 아예 디자인이 다 끝난 완성품 '컴포넌트(Component)'를 통째로 제공합니다. <button class="btn btn-primary">, <div class="modal">, <div class="card"> 처럼 클래스 이름만 치면 완벽하게 예쁜 UI가 튀어나옵니다.
하지만 이 압도적인 편리함은 곧 엄청난 부작용을 낳았습니다.
- "이거 부트스트랩으로 만들었지?": 제공되는 디자인이 너무 강력하고 획일적이라서, 전 세계 수백만 개의 스타트업 웹사이트가 똑같은 폰트, 똑같은 버튼 색상, 똑같은 그림자를 가진 '공장식 복제품'처럼 보이게 되었습니다.
- jQuery(제이쿼리) 종속성: 모달창이나 캐러셀(슬라이더)을 띄우기 위해 부트스트랩은 무거운 jQuery 라이브러리를 강제로 포함시켰습니다(v4까지). React나 Vue 같은 가상 돔(Virtual DOM) 시대가 열리면서 이 무거운 제이쿼리는 프론트엔드의 흉물로 전락했습니다.
3. 디버깅 지옥: !important 덮어쓰기 전쟁
디자이너가 "이 버튼 색깔을 파란색(Primary) 말고 우리 브랜드 컬러인 보라색으로 바꿔주세요"라고 요청하면 지옥이 시작됩니다. 부트스트랩 CSS 파일 안에 이미 .btn-primary { background-color: blue; }가 강하게 쥐어져 있기 때문에, 개발자는 자신의 커스텀 CSS 파일에 .btn-primary { background-color: purple !important; } 라고 억지로 덮어쓰는 코드를 작성해야 합니다.
프로젝트가 커질수록 이 !important 덮어쓰기 파일(Override.css)은 수천 줄로 비대해지며, 결국 "원본 부트스트랩 파일 용량 200kb + 그걸 덮어쓰는 내 파일 용량 100kb"라는 끔찍한 네트워크 낭비와 렌더링 속도 저하를 불러옵니다. Tailwind가 JIT 컴파일러로 필요한 코드 10kb만 남기고 다 날려버리는 것과 비교하면 구시대의 유물로 취급받을 수밖에 없습니다.
정리하며
이번 글에서는 한때 전 세계 웹을 호령했던 부트스트랩의 12-그리드 철학과, 통짜 컴포넌트 프레임워크가 가지는 커스텀(Customizing) 덮어쓰기 지옥의 한계를 상세히 살펴보았습니다. 물론 아직도 빠르고 투박하게 관리자(Admin) 대시보드를 찍어내야 하는 백엔드/풀스택 환경에서는 부트스트랩만큼 든든한 국밥 같은 도구도 없습니다. 자, BEM은 이름 짓기가 너무 고통스럽고, Tailwind는 HTML이 지저분하고, Styled-Components는 런타임 성능이 무겁다면... 정통 CSS 파일의 깔끔함은 유지하면서도 자바스크립트 번들러가 클래스 이름을 자동으로 해시(Hash) 암호화해 주는 타협안은 없을까요? 다음
CSS Modules - Next.js가 선택한 가장 우아한 로컬 스코프(Local Scope) 캡슐화의 정석
이전 포스팅들을 통해 우리는 CSS의 전역 스코프(Global Scope) 충돌을 막기 위해 프론트엔드 진영이 벌여온 처절한 투쟁의 역사를 보았습니다. BEM(.card__title)은 사람이 직접 기괴하게 긴 이름을 지어야 했고, Styled-Components(CSS-in-JS)는 브라우저가 실행될 때마다 JS 엔진이 CSS를 런타임에 뱉어내야 해서 성능(Performance) 오버헤드가 발생했습니다. 그렇다면 "순수한 .css 파일에 코드를 예쁘게 짜면서도, 자바스크립트 번들러(Webpack, Vite)가 빌드할 때 기계가 알아서 BEM처럼 길고 안 겹치는 해시(Hash) 이름으로 변환시켜 주면 안 되나?"라는 기가 막힌 타협안이 등장하게 됩니다. 이것이 바로 현재 React와 Next.js 생태계에서 가장 가벼우면서도 완벽한 캡슐화 도구로 기본 탑재되어 사랑받고 있는 'CSS Modules (CSS 모듈)'입니다. 모듈 CSS를 사용하면 파일 이름에 .module.css라는 꼬리표만 달아주는 순간, 그 파일 안에 적힌 .title, .box 같은 평범한 클래스 이름들이 다른 파일과 절대 겹치지 않는 무적의 고유 명사로 둔갑합니다. 이번 글에서는 CSS 모듈의 해시 치환 원리와 JS 객체 매핑(Mapping)의 비밀, 그리고 CSS-in-JS의 런타임 부하를 완전히 날려버리는 Zero-runtime 렌더링의 강점까지 상세히 알아보겠습니다.
1. 평범함의 위대함: .module.css 확장자의 마법
CSS Modules를 사용하는 방법은 황당할 정도로 간단합니다. 기존에 쓰던 style.css 파일의 이름을 style.module.css로만 바꿔주면 됩니다. 그리고 그 안에는 SCSS나 다른 프레임워크 문법을 배울 필요 없이 우리가 평소에 짜던 100% 순수 CSS(Vanilla CSS)를 작성합니다.
/* Button.module.css */
/* 다른 파일과 겹칠까 봐 .btn-primary-2024 같은 이름 지을 필요 없음! 그냥 .btn! */
.btn {
background-color: blue;
color: white;
border-radius: 8px;
}
.error {
background-color: red;
}
2. 자바스크립트와의 조우: styles 객체 맵핑
이제 React 컴포넌트(JS)에서 이 모듈 CSS 파일을 불러올 차례입니다. 일반 CSS 파일처럼 단순 import './style.css'를 하지 않고, 마치 자바스크립트 라이브러리를 불러오듯이 import styles from ... 형태로 객체(Object)를 가져옵니다.
// Button.jsx (리액트 컴포넌트)
import styles from './Button.module.css';
// styles 객체를 콘솔에 찍어보면?
// { btn: "Button_btn__3x8yZ", error: "Button_error__9fGk1" } 라는 해시 딕셔너리가 들어있습니다!
function Button() {
// 💡 HTML의 class="btn" 대신, 자바스크립트 변수 {styles.btn}을 주입!
return <button className={styles.btn}>클릭하세요</button>;
}
이 과정을 거쳐 브라우저에 최종적으로 렌더링 되는 HTML 태그는 <button class="Button_btn__3x8yZ">가 됩니다. 파일 이름(Button) + 원래 클래스 이름(btn) + 무작위 해시 난수(3x8yZ)가 결합되어 세상에 단 하나뿐인 절대 충돌하지 않는 클래스명이 기계에 의해 자동으로 생성된 것입니다!
3. CSS-in-JS (Styled-Components)를 이긴 결정적 이유: Zero-Runtime
"어? 무작위 해시 클래스명 만들어주는 건 아까 배운 Styled-components랑 똑같은 거 아니야?" 라고 생각하실 수 있습니다. 결과물(캡슐화)은 같지만, 그 클래스 이름을 '언제' 만드느냐(Timing)에서 성능이 극명하게 갈립니다.
- Styled-Components (Runtime): 사용자가 웹사이트에 접속해서 브라우저가 켜져 있는 동안, 자바스크립트 엔진이 땀을 뻘뻘 흘리며 실시간으로 CSS를 파싱하고 해시 클래스를 생성해
<style>태그에 주사합니다. 앱이 커질수록 렌더링 속도가 눈에 띄게 느려집니다 (특히 저사양 폰에서). - CSS Modules (Build-time / Zero-runtime): 사용자가 사이트에 접속하기 한참 전, 개발자가 코드를 짜고 서버(Vercel, Netlify)에 빌드(배포)하는 그 시점에 웹팩(Webpack)이 모든 해시 변환을 끝내버립니다. 사용자 브라우저에는 이미 최적화가 다 끝난 순수한(정적)
.css파일과.js파일이 전송되므로, 브라우저 엔진은 자바스크립트 파싱 딜레이 없이 0.001초 만에 CSS를 즉시 렌더링 합니다. 이 엄청난 성능 차이 때문에 SSR(서버 사이드 렌더링)의 제왕인 Next.js가 CSS Modules를 공식 디폴트(Default) 스타일링 방안으로 채택한 것입니다.
4. 글로벌을 향한 탈출구: :global()
모든 클래스가 해시로 암호화되는 것은 강력하지만, 가끔은 부작용이 있습니다. 만약 내부에 플러그인(예: 캘린더 라이브러리)을 넣었는데 그 플러그인의 특정 클래스(.react-calendar)를 덮어쓰고 싶다면 어떻게 할까요? 내가 .react-calendar라고 적어도 .Calendar_react-calendar__2Xj3로 변환되어 버리니 덮어쓸 수가 없습니다.
이때 CSS 모듈은 :global(.react-calendar) { color: red; } 라는 탈출구 함수를 제공합니다. :global 괄호 안에 적힌 클래스는 해시 암호화를 거치지 않고 원본 이름 그대로(전역 스코프) 살려두어, 외부 라이브러리의 클래스를 정확하게 타겟팅(Overriding)할 수 있게 해줍니다.
정리하며
이번 글에서는 BEM의 작명 스트레스를 지워버리고, CSS-in-JS의 런타임 성능 저하까지 완벽하게 해결한 타협의 예술, CSS Modules의 해시 맵핑 원리에 대해 상세히 살펴보았습니다. 테일윈드의 더러운 HTML이 싫고, 순수 CSS의 깔끔한 구조를 유지하면서도 리액트 생태계의 캡슐화를 누리고 싶다면 CSS 모듈은 완벽한 정답입니다. 자, 이렇게 프레임워크와 아키텍처 이야기까지 끝났습니다. 그런데 우리가 화려하게 짠 웹사이트가 구글 검색(SEO)에서 1페이지에 노출되지 않거나, 시각 장애인이 스크린 리더로 접근했을 때 읽을 수 없는 쓰레기 정보만 가득하다면 그 사이트는 실패한 것입니다. 시각적 디자인을 넘어선 기계(로봇)와의 소통! 다음
'StyleSheet > CSS' 카테고리의 다른 글
| [CSS] CSS 실전 웹 UI 프로젝트 (모달, 스크롤, SEO 최적화) (1) | 2026.08.10 |
|---|---|
| [CSS] 성능 최적화와 디버깅 (0) | 2026.08.10 |
| [CSS] CSS 아키텍처와 방법론 (BEM, Sass/SCSS) (0) | 2026.08.10 |
| [CSS] CSS 변수(Variables)와 테마 관리 (0) | 2026.08.10 |
| [CSS] CSS 애니메이션 완벽 가이드 (Transform, Transition, Keyframes) (0) | 2026.08.10 |