목차
개요
현대의 웹 애플리케이션들에서는 수많은 컨텐츠와 사용자와의 상호작용이 존재합니다. 따라서 ‘CSS 작성 방법, 렌더링 성능, 유지보수 용이성’ 등 스타일링에 관련된 충분한 이해도가 있어야 서비스의 품질을 높게 유지할 수 있습니다.
따라서 오늘은 Next.js 프로젝트에서 스타일링을 적용할 수 있는 다양한 방식의 장단점을 알아보고 각 상황에 맞는 최선의 선택을 찾아보겠습니다.
Next.js의 스타일링
Styled JSX
우선 Next.js는 React 웹 프레임워크이기 때문에, 기본적으로 Styled JSX를 사용할 수 있습니다. 이는 JSX 내부에 css 문법을 작성하는 방식으로, 컴포넌트 단위로 작성해서 해당 컴포넌트로 스코프가 적용됩니다.
위와 같은 특징 때문에 styled JSX는 컴포넌트 별로 스타링일을 적용하기 편하고, css 문법을 그대로 사용하기 때문에 러닝커브가 거의 없습니다. 또한 일반 css에 가까운만큼 렌더링 성능이 뛰어납니다.
단점은 한번 작성한 CSS를 재사용하기 어렵다는 특징이 있습니다. 그리고 기존 CSS와 마찬가지로 class name을 생성해서 매핑시켜주는 방식으로 스타일링을 적용해야 했는데, 이 class naming을 굉장히 번거로워하는 개발자들이 존재합니다.
CSS-in-JS
그리고 2014년 facebook의 개발자인 Christopher Chedeau의 발표에서 처음으로 CSS-in-JS라는 개념이 공개되었고, 다음과 같이 기존 CSS의 어려움을 보완했다고 설명했습니다.
- Global namespace
class명이 빌드 타임에 유니크하게 변경되기 때문에 별도의 명명 규칙이 필요치 않음
- Dependencies
CSS가 컴포넌트 단위로 추상화되기 때문에 CSS(모듈)간 의존성 고려가 필요없음
- Dead Code Elimination
(주로) 컴포넌트와 CSS가 동일한 파일내에 존재하기 때문에 수정 및 삭제가 용이
- Minification
빌드 타임에 짧은 길이의 유니크한 클래스를 자동으로 생성하여 문서의 볼륨을 낮춰줌
- Sharing Constants
CSS 코드가 JS에 작성되므로 컴포넌트 상태 공유가 가능
- Non-deterministic Resolution
CSS가 컴포넌트 스코프로 적용되므로 우선순위에 따른 문제가 없음
- Isolation
CSS가 컴포넌트에 격리되어 있기 때문에 상속 문제가 없음
Styled Components
대표적인 CSS-in-JS 라이브러리인 Styled Components는 템플릿 리터럴 안에 css 문법을 입력해서 JS 코드로 css를 적용하는 방식을 가지고 있습니다. 컴포넌트에 css를 따라 매핑해줄 필요 없이 컴포넌트 자체가 스타일링이 적용된 UI를 가지고 있기 떄문에, 컴포넌트 단위의 직관적인 스타일링이 가능합니다. 또한 props로 변수를 전달받아서 동적으로 css를 적용하는 것도 가능하기 때문에, 재사용성에 매우 유리하다고 볼 수 있습니다.
CSS Modules
CSS 모듈은 CSS를 모듈화해서 관리하는 방식입니다. 정의한 CSS 클래스마다 자동으로 고유한 class name을 생성해서 해당 class를 로컬 스코프로 제한함으로써 naming 충돌을 예방해줍니다. 따라서 모듈 간에 동일한 클래스 네임으로 중복될 걱정이 필요 없고, 컴포넌트 단위로 css 문법을 분리해서 작성하기 용이합니다.
Next.js에서는 CSS 모듈을 기본적으로 내장하고 있기 때문에, 바로 사용이 가능합니다. *.module.css 확장자를 가진 파일을 생성해서 CSS 문법을 작성하면 됩니다. 그리고 다른 파일에서 import 해서 style.dashboard 또는 style[“dashboard”]의 형태로 clsssName에 입력해서 스타일을 적용할 수 있습니다.
빌드 타임에서 CSS Module 파일들은 자동으로 minify, code split, bundling이 적용되어 css 파일로 변환됩니다. 이러한 css 파일들은 애플리케이션이 실행하는 가장 빠른 실행 경로이며, 이를 통해 실행되어야 하는 필수 css들만 효율적으로 로드되는 것을 보장해줍니다.
CSS 모듈을 사용하더라도 Global CSS와 외부 CSS는 동시에 사용할 수 있기 때문에, 전역적으로 적용해야하는 CSS의 경우는 Root Layout 파일에 그대로 적용하면 됩니다.
CSS Preprocessor
CSS 전처리기는 vanila CSS 문법의 불편함을 보완하기 위해, 변수, 함수, 상속 등의 개념을 도입하여 특별한 문법으로 css 코드를 작성할 수 있도록 해줍니다. 이를 통해 반복되는 요소를 변수 또는 함수로 대체할 수 있고, 중첩, 상속으로 코드를 구조화해서 관리하기 편리합니다.
가장 유명한 CSS 전처리기로 SASS(Syntactically Awesome Style Sheets)이 있고, 여기에 SCSS가 포함되어 있습니다. sass는 들여쓰기와 줄바꿈으로 코드를 작성하고, scss는 중괄호를 사용하여 코드를 작성하는 방식을 가지고 있습니다. Next.js에서는 기존 CSS 문법과 동일하게 중괄호를 사용하는 scss 를 사용할 것을 권장하고 있고, scss가 보편적으로 거의 대부분 사용되고 있습니다.
웹 브라우저와 서버는 CSS 파일만 읽을 수 있기 때문에, 작성된 SCSS 파일을 CSS 파일로 변환해주는 컴파일러가 필요합니다. Visual Studio Code에서는 Live Sass Compiler라는 확장 도구를 지원하고 있어서, IDE에서 SCSS 코드를 저장하면 바로 CSS로 컴파일해주고 있습니다.
_.scss 확장자 파일을 생성해서 바로 scss 코드를 작성할 수 있고, _.module.scss와 같이 CSS 모듈과 함께 사용할 수 있습니다.
Utility-First CSS
CSS가 오직 웹 문서를 스타일링하기 위한 목적으로 사용될 때는 inline css 스타일을 적용하는 것이 유지보수에 매우 취약했습니다. 왜냐하면, 코드가 너무 비대해지고 가독성이 떨어졌기 때문입니다.
따라서 그러한 관점에서는 html과 css를 분리하고, 그 둘 사이를 매핑해주는 클래스명을 시멘틱하게 지어서, 시각적 기능이 아닌 의미 기반으로 네이밍을 하는 것이 재사용성과 유지보수 측면에서 더 나은 선택이었습니다.
하지만 자주 사용하고 변하지 않는 시각적 기능을 미리 정해두어서, 모든 css를 이것으로만 적용한다면 오히려 유지보수에 더 용이할 것이라는 의견이 나왔습니다. 특히 의미론적 네이밍이 어렵고 BEM 방식 등의 네이밍 규칙이 오히려 불편하게 느껴지는 분들도 점점 많아졌습니다.
따라서 미리 클래스명을 만들어놓고 inline css 형식으로 html에 바로 넣어서 사용하자는 ‘Utility-First CSS (Atomic CSS)’ 방식이 다시 대두되기 시작한 것입니다. 단, 과거의 inline style과 달라진 점은 바로 컴포넌트 기반의 개발로 트렌드가 바뀌면서, 웹 프레임워크가 중복방지와 시멘틱에 대해서 보완해주고 있다는 점입니다.
이제는 코드가 좀 비대해지더라도 컴포넌트로 모듈화되어 있기 때문에, 단점이 커버되고 장점을 충분히 활용할 수 있는 환경이 된 것입니다.
Tailwind
대표적인 Utility-First CSS 라이브러리가 바로 Tailwind입니다. 위 설명 그대로 시각적 기능을 기준으로 자주 사용하는 class name을 정해두었고, 이를 그대로 class에 넣어주면 바로 스타일이 적용됩니다.
반응형, 다크 모드, 테마 등을 적용하기에 용이하며, 컴포넌트 단위로 개발하기 좋습니다. 또한 빌드 타임에 사용되지 않는 CSS를 자동으로 제거하여 성능 면에서도 유리하다는 것을 알 수 있습니다.
어떤 방식을 선택해야 할까?
저는 스타일링 방식을 선택할 때, 크게 2가지 항목을 고려해야 한다고 생각합니다.
렌더링 속도
사용자 경험에서 렌더링 속도는 매우 중요합니다.
그런데 CSS-in-JS의 가장 큰 단점은 렌더링 속도 저하입니다. CSS-in-JS 방식은 JS로 CSS를 작성하기 때문에, 페이지 로딩 시 구문 분석 과정이 필요합니다.
반면 CSS Module과 Tailwind와 같은 방식은 빌드타임에 온전한 CSS 파일로 변환되므로 추가적 구문 분석이 필요치 않습니다. CSS 비중이 작은 페이지라면 체감하는 속도 차이가 크지 않겠지만, 결국 스타일링이 많고 규모가 큰 애플리케이션이 될 수록 성능 문제가 발생할 수 있습니다.
CSS-in-JS는 개발자 입장에서는 조금 더 편리하게 개발을 진행할 수 있습니다. 하지만 확장성 면에서 결국 성능을 생각하지 않을 수 없습니다. 따라서 마크업과 JS를 동시에 작성해야 하는 소규모의 팀에서는 CSS-in-JS 강력한 성능을 발휘할 수 있습니다. 초기에 빠르고 효율적으로 개발을 진행할 수 있습니다.
하지만 규모가 커질 예정이며 안정적인 유지보수가 필요한 팀이라면 초기부터 CSS Module이나 tailwind와 같은 유틸리티 CSS가 더 나은 선택일 수 있습니다.
개발 규모 및 인원
현재 Next.js에서는 CSS 모듈과 Tailwind는 기본으로 내장되어 지원하고 있기 때문에, 이 2가지 방식을 강력하게 추천하고 있다는 것을 알 수 있습니다.
그렇다면 CSS Module과 Tailwind 중에서는 어떤 방식을 선택해야 할까요?
스타일 컨벤션 존재 여부
위에서 Tailwind와 같은 Utility-First CSS 코드가 비대해진다는 단점을 커버할 정도로 시각적 기능 기반의 클래스 네이밍이 효과적이라서 사용한다고 했습니다. 그리고 반복적으로 사용하고 고정되어 있는 클래스 네임을 미리 정해두고 사용하는 것입니다.
그런데 만약 SI 기업일 경우에는 고객사마다 원하는 스타일이 달라질 수 있고, 다양한 기술과 구조를 가진 프로젝트들이 존재합니다. 따라서 자체 서비스를 개발하는 인하우스와 다르게 them, color, font에 대한 세부화된 규칙, 즉 스타일 컨벤션이 존재하지 않습니다. 미리 협의된 스타일이 정해지지 않는다면, Utility-First 방식은 오히려 일반 CSS에 또 다른 문법을 입혀서 작성하는 이중 작업이 될 수도 있습니다.
CSS 문법 숙련도
새로운 프로젝트에 스타일링을 적용할 때, 우리는 처음부터 새로 만들지 않습니다. 모든 문법과 패턴을 기억할 수 없기 때문에, 본인이 예전에 진행했던 프로젝트 또는 다른 사람이 작성한 코드를 reference 삼아서 개발을 진행합니다.
그런데 신입 개발자 입장에서는 일반 CSS 문법에 대한 경험과 이해도가 충분히 쌓이지 않은 시점에서 Tailwind 문법으로 CSS를 작성해야 한다면, 기존 프로젝트들이 reference로 사용되기 어렵습니다. 두 가지 방식을 한번에 익히는 과정에서 혼란을 가중시킬 수도 있다고 생각합니다. 특히 초반에 Tailwind 만의 클래스명을 일일이 탐색해서 적용하는데 생각보다 많은 공수가 필요합니다.
따라서 Tailwind는 이미 CSS를 잘 다루는 개발자가 더 효율적인 방식을 찾는 과정에 더 적합한 언어라고 생각합니다. 주니어 개발자가 실무를 진행하고 있는 환경에서는 유지보수에 어려움이 생겨서 오히려 효율성을 저하시킬 수 있습니다.