안녕하세요! 웹 파트 한수정입니다.
지난 기수 앱잼을 진행하면서 팀원들과 함께 CSS에 대해 많이 고민하고 조사했던 기억이 있습니다.
실제로 다른 팀들도 CSS 선택 이유를 분명히 하려는 노력을 했고, 그중 많은 팀들이 Vanilla Extract를 선택했습니다.
zero runtime CSS-in-JS라고 하는 Vanilla Extract를 어쩌다 선택하게 되었는지 알아보겠습니다 ㅎㅎ
먼저 CSS가 브라우저에 적용되는 과정을 살펴보고, CSS와 CSS-in-JS에 대해 정리해보려고 합니다!
🔍 웹 페이지가 브라우저에 렌더링되는 과정
1. 불러오기
브라우저가 HTML, CSS, JS, 이미지 등 필요한 리소스를 요청하고 서버에서 응답을 받아옵니다.
2. DOM,CSSSOM 생성
HTML은 웹 엔진의 HTML 파서를 통해 DOM Tree로 변환되고 CSS는 CSS 파서를 통해 CSSOM 트리로 변환됩니다.
3. Render Tree 생성
DOM Tree와 CSSOM Tree를 결합하여 렌더 트리를 생성합니다.
4. Layout 단계
Render Tree를 기반으로 각 요소의 크기와 위치를 계산합니다.
5. Paint 단계
계산된 정보를 바탕으로 실제 화면에 요소를 그립니다.
이 과정에서 CSS 와 CSS-in-JS는 CSSOM 생성과 렌더 트리 생성에 각각 다른 방식으로 관여합니다.
🔍 1주차 과제에서 사용하는 CSS에서 CSSOM 생성 흐름 자세히 보기
전통적 방식의 CSS는 HTML 외부에 스타일 정의를 두거나 HTML 태그 내에서 스타일을 정의하는 방식입니다.
예를 들어, 아래처럼 <head>에 CSS 파일을 연결하면 브라우저는 HTML 파싱 도중 CSS 파일을 불러오게 됩니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>1주차 아티클</title>
<link rel="stylesheet" href="style.css"/>
</head>
<body>
</body>
</html>
렌더링 엔진은 meta 태그까지 HTML을 순차적으로 해석한 다음, link 태그를 만나면 DOM 생성을 일시 중단하고 link 태그의 href 어트리뷰트에 지정된 CSS 파일을 서버에 요청합니다. 서버로부터 CSS 파일이 응답되면 렌더링 엔진은 HTML과 동일한 해석과정을 거쳐 CSS를 파싱하여 CSSOM을 생성합니다. CSSOM은 CSS의 상속을 반영하여 생성됩니다.
body {
font-size: 18px;
}
ul {
list-style-type: none;
}
위와 같이 스타일이 있던 경우 아래와 같이 CSSOM이 생성됩니다.
CSS는 정적 자원으로 처리되어 브라우저의 캐시를 활용하여 네트워크 비용을 줄일 수 있습니다. 따라서 같은 CSS 파일이 여러 페이지에서 사용될 경우, 추가 네트워크 요청 없이 빠르게 로드할 수 있어 성능 최적화의 중요한 요소입니다.
🔍 CSS-in-JS
CSS-in-JS는 JavaScript 코드 안에서 CSS 스타일을 작성하는 방식입니다.
전통적인 CSS는 별도의 .css 파일로 스타일을 정의하고 HTML 문서에서 이를 참조하는 방식이지만, CSS-in-JS는 JavaScript 파일 내에서 컴포넌트 단위로 스타일을 정의할 수 있어 재사용성과 유지보수성이 크게 향상됩니다.
CSS-in-JS를 처음 소개한 Vjeux는 CSS를 작성하는 아래와 같은 어려움을 CSS-in-JS로 해결할 수 있다고 말했습니다.
- Global namespace: 글로벌 공간에 선언된 이름의 명명 규칙 필요
- Dependencies: CSS간의 의존 관계를 관리
- Dead Code Elimination: 미사용 코드 검출
- Minification: 클래스 이름의 최소화
- Sharing Constants: JS와 CSS의 상태 공유
- Non-deterministic Resolution: CSS 로드 우선 순위 이슈
- Isolation: CSS와 JS의 상속에 따른 격리 필요 이슈
CSS-in-JS 라이브러리 중, styled-components를 사용하면 아래와 같이 컴포넌트 안에 스타일을 작성할 수 있습니다.
import styled from 'styled-components';
const Text = styled.div`
color: white,
background: black
`
<Text>CSS-in-JS, styled-components</Text>
위 코드는 실행 시, 다음과 같이 변환되어 브라우저에 적용됩니다.
<style>
.hash136s21 {
background-color: black;
color: white;
}
</style>
<p class="hash136s21">CSS-in-JS, styled-components</p>
CSS-in-JS는 런타임 시 동적으로 스타일을 생성합니다. 이 과정은 JavaScript가 실행되며 시작되고, 스타일 정의는 해당 시점에 <style> 태그로 변환되어 DOM에 삽입됩니다. 이후 브라우저는 이 <style> 태그의 내용을 읽어 CSS 파싱을 진행하고, 이를 통해 CSSOM을 생성합니다.
즉, CSSOM 생성이 HTML 파싱 중에 이루어지는 CSS 방식과 달리, CSS-in-JS에서는 JavaScript가 실행된 이후에야 스타일이 적용되기 시작합니다. 이로 인해 초기 렌더링이 지연될 수 있고, 런타임 성능에도 영향을 줄 수 있습니다. CSS-in-JS의 런타임 스타일 생성은 JavaScript 실행 시간이 길어지는 런타임 오버헤드를 발생시킬 수도 있습니다.
🔍 Zero-runtime CSS-in-JS
CSS-in-JS의 런타임 성능 이슈를 해결하기 위해, 한계를 보완하기 위해 등장한 것이 바로 Zero-runtime 방식의 CSS-in-JS입니다. 이 방식은 JavaScript 실행 시점이 아닌 빌드 타임에 미리 스타일을 생성하고 정적 CSS로 추출하기 때문에, 런타임에 별도의 작업 없이 바로 CSSOM 생성을 진행할 수 있습니다.
따라서 런타입 스타일 생성이 없어 JS가 실행되기 전에도 CSSOM을 빠르게 구성할 수 있어 초기 렌더링 속도가 향상 되고 JS 번들 크기가 감소합니다. 또한, 런타임 의존이 없어 SSR 환경에서도 성능 저하 없이 사용이 가능합니다.
대표적인 zero-runtime CSS-in-JS 도구로는 Vanilla Extract이 있습니다.
// styles.css.ts
import { style } from '@vanilla-extract/css';
export const heading = style({
color: 'navy',
backgroudColor: 'white',
});
// App.tsx
import { heading } from './styles.css.ts';
export function App() {
return <h1 className={heading}>Zero-runtime, CSS-in-JS, Vanilla Extract</h1>;
}
위 코드에서 작성한 스타일은 빌드 시 .css 파일로 스타일이 추출되며, 런타임에는 별도의 JS 스타일 처리 없이 HTML에 <link>로 연결되는 방식입니다.
<link rel="stylesheet" href="/build/styles.css">
<h1 class="heading_hash123">Zero-runtime, CSS-in-JS with Vanilla Extract</h1>
따라서 브라우저는 JavaScript 실행 이전에도 CSSOM을 구성할 수 있고, 최적화된 성능과 안정적인 스타일 적용이 가능합니다.
덕분에 최근 많은 프론트엔드 개발자들이 Vanilla Extract와 같은 Zero-runtime CSS-in-JS 도구를 선택하고 있습니다.
모두들 근거 있는 선택을 통해 자신에게 딱 맞는 CSS 방식을 찾으시길 응원하겠습니다.
읽어주셔서 감사합니다!
https://www.samsungsds.com/kr/insights/web_component.html
찾다보니 css에 관해 더욱 좋은 아티클이 있길래 공유합니다 !
https://pozafly.github.io/css/explore-how-to-apply-modern-css/
'1주차' 카테고리의 다른 글
[HTML] 접근성을 위한 또 다른 방법 - ARIA Role (0) | 2025.04.11 |
---|---|
HTML 렌더링 중 JavaScript가 실행되면 왜 렌더링이 멈출까? (0) | 2025.04.11 |
웹뷰와 PWA (0) | 2025.04.11 |
웹 성능 최적화 핵심 3가지 (0) | 2025.04.11 |
클라이언트, 서버, API, HTTP, REST 등.. (0) | 2025.04.11 |