[CSS] 왜 Next.js는 tailwind를 추천할까

thru·2023년 10월 22일
6

emotion 아이콘 몬생김


서론

Next.js 공식 문서를 보면서 tailwind를 적극 추천하는 느낌이 들었다. 또 Next.js를 사용할 거면 styled component 같은 건 추천하지 않는다고 해서 이유를 알고 싶었다.


CSS-in-JS

목적

기존의 selector 기반 CSS는 다음과 같은 문제점을 설계상 가지고 있었다.

  • Global namespace: 모든 스타일이 전역에 선언되기 때문에 겹치지 않는 class name의 필요
  • Dependencies: 한 요소에 여러 CSS 규칙이 적용 가능하므로 관리가 어려움
  • Dead Code Elimination: CSS가 JS와 분리되어 있으므로 기능 수정사항을 수동으로 동기화하는 어려움
  • Minification: 겹치지 않게 class name 지정하려다 보니 길어지는 문제
  • Sharing Constants: CSS가 분리돼 있어 JS의 상태 값을 공유하기 어려운 문제
  • Non-deterministic Resolution: CSS 로드 순서에 따라 우선순위가 달라지는 문제
  • Isolation: CSS는 부모로부터 스타일을 상속하므로 하위 컴포넌트에 영향

CSS-in-JS는 JS를 통해 CSS를 생성함으로써 위 문제를 커버하려는 방법이다.

  • JS 내부에서 선언: 기능 수정 시 동기화가 용이, 상태 값 공유 가능
  • 컴포넌트 단위로 적용: 한 요소에는 한 규칙, 로드 순서가 우선 순위에 영향을 주지 않음, 상속 제거
  • class name 자동 생성: 빌드 타임에 짧고 유일한 이름을 지어줌

구현 방식

대부분의 CSS-in-JS 라이브러리들이 쓰는 방식은 Runtime CSS-in-JS 또는 Runtime stylesheets 라고 한다. 스타일을 정의하는 코드가 클라이언트 런타임 때 실행되는 JS 번들에 포함되는 방식인데 브라우저는 스타일 코드를 해석하지 못하므로 라이브러리의 코드도 JS 번들에 포함되어야 한다. 해석된 스타일 코드는 document의 head 태그에 style 태그를 추가하거나CSSStyleSheet API로 직접 CSSOM에 적용시켜서 사용한다.

이러한 방식으로 인해 단점이 두 가지 존재한다.

  • JS 번들 용량 증가: 스타일 코드와 라이브러리 런타임 코드가 클라이언트로 전달되어야 한다.
  • 페이지 렌더링 시간 증가: JS에 작성된 CSS 코드를 구문 분석하고 동적으로 추가하면서 Scripting 시간 증가

단점이 있긴 하지만 장점으로 얻는 개발용이성이 워낙 커서 React의 인기와 함께 유행했다고 한다. 그러나 CSR 환경을 바탕으로 두고 구상한 방법이었기 때문에 SSR 환경에서 문제가 나타났다.


SSR에서의 CSS-in-JS

기본적으로 SSR에서 CSS-in-JS 라이브러리를 그대로 사용하면 hydrate 이전 서버에서 받아오는 HTML에 스타일이 전혀 적용되지 않아 잠깐 날 것의 HTML이 나타나는 문제가 있다. 때문에 초기 HTML에 포함되는 요소에 대한 CSS인 Critical CSS를 서버쪽에서 사용할 수 있도록 하는 처리가 필요하다. 보통 SSR 과정에서 정적으로 생성되는 요소의 CSS만 추출해서 HTML에 적용하도록 설정한다.

위 방법도 문제를 완벽히 해결하지는 못한다. Critical CSS에 대한 코드는 클라이언트 쪽에서 실행될 JS에도 포함되어야 한다. 때문에 동일한 스타일에 대한 코드가 초기 HTML에서 한 번, JS 번들에서 두 번 클라이언트에게 전달된다.

또한 SSR 방향성이 페이지에서 컴포넌트로 넘어간 Next.js 13 버전에서는 대부분 사용이 제한적이다. 이는 SSR 처리를 한 페이지에 해당되는 HTML를 활용하는 방식으로 구현했기에 서버 컴포넌트와 방향이 충돌하는 것으로 보인다. 클라이언트 컴포넌트에서 사용하는 것도 styleRegistry를 생성해서 HTML에 넣도록 하는 별도의 wrapper 컴포넌트를 설정하는 과정이 필요해서 비교적 번거롭다.

심지어 기존에 SSR 처리를 간단하게 추상화해두었던 Emotion은 Next.js 13 버전에선 아직 지원 작업 중이다. 프레임워크의 방향성에 따라 지원 작업을 발 맞춰야한다는 것도 CSS-in-JS의 약점일 것 같다.


Tailwind CSS

Tailwind CSS는 Utility CSS로 class name을 컴포넌트가 아니라 기능에 붙임으로써 CSS의 문제를 해결하려 했다. 예전에 유명했던 Bootstrap과 유사하게 미리 정의된 스타일 구성 요소를 가져와서 사용하는 방식으로 동작한다. 대신 utility CSS이므로 필요에 따라 확장성있게 CSS를 작성할 수 있고, 빌드 시에 사용하지 않는 클래스는 제거되어 번들 크기에 주는 영향도 줄일 수 있다. 또한 atomic한 특성으로 인해 프로젝트의 크기가 거대해져도 스타일시트의 크기가 비례해서 늘어나지 않는 이점도 있다.

SSR 관점에서 중요한 건 런타임에 스타일시트를 생성하지 않고 빌드 타임에 스타일시트를 가져오는 방식이라는 점이다. 때문에 SSR에서도 추가적인 설정 없이 작동할 수 있다.

더 전통적인 방식인 CSS module이나 sass도 똑같이 빌드 타임에 스타일시트를 붙이지만 selector 기반 CSS의 한계를 극복하지 못했기 때문에 tailwind css를 콕 집어서 추천해주고 있는 것 같다.

물론 CSS-in-JS의 장점인 동적 변수를 사용할 수 없고 animation과 transition 사용에 제약이 있어서 완벽히 대체하진 못한다고 한다. 이 이슈들은 inline style이나 CSS module 등 다른 방식과 병행 사용하면서 해결하는 것으로 보인다.


여담: Zero Runtime CSS-in-JS

사실 CSS-in-JS에는 소수에 해당하는 다른 방식도 있는데 Zero Runtime 또는 Static CSS Extration, Compile time CSS-in-JS 으로 불리는 방식이다. 말그대로 런타임에 스타일을 생성하지 않는다. 대신 JS 파일에 작성된 스타일 코드를 빌드 타임에 CSS 파일로 분리해서 적용한다. 때문에 SSR에서 Runtime CSS-in-JS 보단 문제가 적다.

다만 Next.js 13 버전에서는 이 방식도 클라이언트 컴포넌트에서만 사용이 가능하다고 한다.

어찌됐든 CSS 코드를 추출하는 과정이 필요하기 때문이 아닐까 싶다.


결론

SSR 환경에서 불안정한 CSS-in-JS 방식을 제외하고 남는 CSS 솔루션 중에서 Tailwind CSS가 가장 CSS의 단점을 잘 커버할 수 있었기 때문에 추천되고 있는 것 같다.


참조


profile
프론트 공부 중

0개의 댓글