안동민 개발노트

안동민 개발노트

CSS 모듈 활용Sass 통합CSS-in-JS 솔루션Tailwind CSS 설정 및 사용
본문 시작
  1. 홈
  2. 문서
  3. Next.js
  4. 9장 : 스타일링과 CSS
  5. CSS 모듈 활용
  1. Next.js
  2. CSS 모듈 활용

CSS 모듈 활용

module.css의 지역화된 클래스 이름으로 스타일 충돌을 막고 전역 CSS·SCSS와의 책임 범위를 구분합니다.

웹 애플리케이션의 화면 구조와 사용자 경험은 스타일링(Styling) 방식에 영향을 받습니다.

React 기반의 Next.js 애플리케이션에서 스타일을 적용하는 방법은 다양하며, CSS 모듈(CSS Modules)은 클래스 이름을 파일 단위로 스코프 처리해 스타일 충돌을 줄입니다.

이 절에서는 CSS 모듈의 개념과 필요성, Next.js 프로젝트에서 활용하는 방법을 정리합니다.


CSS 모듈이란 무엇인가요?

CSS 모듈은 기본적으로 클래스 이름과 애니메이션 이름을 파일별 고유 이름으로 바꾸고, 컴포넌트가 그 이름을 가져와 사용하는 방식입니다.

서로 다른 모듈에서 같은 클래스 이름을 작성해도 이름 충돌을 줄일 수 있습니다.

핵심 특징
  • 로컬 스코프(Local Scope) 이름: 서로 다른 모듈의 같은 클래스 이름이 서로 다른 출력 이름으로 연결됩니다. 생성된 클래스는 이를 적용한 DOM 요소에 작용합니다.
  • 고유한 클래스 이름 생성: 빌드 도구가 출력 클래스 이름을 생성합니다. 이름의 구체적인 형식은 도구와 개발·운영 모드에 따라 달라질 수 있으므로 직접 작성하지 않고 styles.btn처럼 참조합니다.
  • 파일 기반 모듈화: CSS 파일과 이를 가져오는 컴포넌트의 의존성을 드러냅니다. 같은 모듈을 여러 컴포넌트에서 공유할 수도 있습니다.
  • JavaScript를 통한 스타일 임포트: CSS 파일을 JavaScript/TypeScript 코드에서 직접 import하여 사용합니다.

왜 CSS 모듈을 사용해야 할까요?

전통적인 CSS 작성 방식이나 다른 스타일링 방법에는 다음과 같은 문제점들이 있었습니다.

전역 스코프 문제 (Global Scope Pollution)
  • 모든 CSS 클래스 이름은 기본적으로 전역 스코프를 가집니다.
  • 다른 컴포넌트나 페이지에서 의도치 않게 동일한 클래스 이름을 사용하면 스타일이 덮어씌워지는 충돌(Collision)이 발생합니다.
  • 이는 특히 규모가 큰 프로젝트나 여러 개발자가 협업하는 환경에서 디버깅을 어렵게 하고 유지보수를 복잡하게 만듭니다.
스타일 간의 의존성 및 관리의 어려움
  • 어떤 스타일이 어떤 HTML 요소에 적용될지 파악하기 어렵습니다.
  • 컴포넌트 삭제 시 해당 스타일을 안전하게 삭제할 수 있는지 확신하기 어렵습니다. (데드 코드)
CSS 모듈은 이러한 문제들을 다음과 같이 해결합니다
  • 이름 충돌 감소: 다른 모듈의 클래스 이름을 전역에서 선점할 필요가 없습니다.
  • 명확한 의존성: 컴포넌트 파일에서 CSS 파일을 직접 임포트하므로 스타일 사용처를 추적하기 쉽습니다.

Next.js에서 CSS 모듈 사용하기

Next.js는 CSS 모듈을 기본적으로 지원하며, 추가 설정 없이 바로 사용할 수 있습니다.

CSS 파일은 [name].module.css로 작성합니다. .module.scss와 .module.sass는 뒤에서 설명하는 sass 패키지를 설치한 후 사용할 수 있습니다.

실습: 버튼 컴포넌트에 CSS 모듈 적용하기

간단한 버튼 컴포넌트를 만들고 CSS 모듈을 사용하여 스타일을 적용해 봅시다.

src/app/css-modules/page.tsx 파일 생성 (서버 컴포넌트): 이 페이지는 서버 컴포넌트이며, 우리가 만들 클라이언트 컴포넌트 StyledButton을 임포트하여 사용합니다.

src/app/css-modules/page.tsx
// src/app/css-modules/page.tsx

import StyledButton from './StyledButton'; // 클라이언트 컴포넌트 임포트
import styles from './page.module.css'; // 페이지 요소에 적용할 CSS 모듈 임포트

export default function CssModulesPage() {
  return (
    <div className={styles.container}>
      <h1 className={styles.title}>CSS 모듈 사용 예제</h1>
      <p className={styles.description}>
        아래 버튼은 CSS 모듈을 사용하여 고유한 스타일을 가집니다.
      </p>
      <div style={{ display: 'flex', gap: '20px', marginTop: '30px' }}>
        <StyledButton label="클릭하세요" />
        <StyledButton label="다른 버튼" primary={true} />
      </div>
    </div>
  );
}

src/app/css-modules/page.module.css 파일 생성: page.tsx에 적용될 기본적인 레이아웃 스타일을 정의합니다.

src/app/css-modules/page.module.css
/* src/app/css-modules/page.module.css */
.container {
  padding: 40px;
  max-width: 800px;
  margin: 20px auto;
  background-color: #f8f8f8;
  border-radius: 10px;
  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
  text-align: center;
}

.title {
  color: #333;
  margin-bottom: 15px;
  font-size: 2.5em;
}

.description {
  color: #666;
  font-size: 1.1em;
  line-height: 1.6;
}

src/app/css-modules/StyledButton.tsx 파일 생성 (클라이언트 컴포넌트): 버튼 컴포넌트와 해당 스타일을 정의합니다.

src/app/css-modules/StyledButton.tsx
"use client"; // 클라이언트 컴포넌트임을 명시

import React from 'react';
import buttonStyles from './StyledButton.module.css'; // 🚨 CSS 모듈 임포트

interface StyledButtonProps {
  label: string;
  primary?: boolean;
  onClick?: () => void;
}

export default function StyledButton({ label, primary = false, onClick }: StyledButtonProps) {
  // CSS 모듈의 클래스 이름을 조합합니다.
  return (
    <button
      className={`${buttonStyles.btn} ${primary ? buttonStyles.primary : ''}`}
      onClick={onClick}
    >
      {label}
    </button>
  );
}

src/app/css-modules/StyledButton.module.css 파일 생성: StyledButton 컴포넌트에 적용될 스타일을 정의합니다.

src/app/css-modules/StyledButton.module.css
.btn {
  padding: 12px 25px;
  font-size: 1.1em;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transition: background-color 0.3s ease, transform 0.1s ease;
  font-weight: bold;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}

.btn:hover {
  transform: translateY(-2px);
}

.btn:active {
  transform: translateY(0);
  box-shadow: none;
}

/* 기본 버튼 스타일 */
.btn {
  background-color: #007bff;
  color: white;
}

/* primary prop이 true일 때 적용될 스타일 */
.primary {
  background-color: #28a745;
  color: white;
}

.primary:hover {
  background-color: #218838;
}

실습 확인: 개발 서버(npm run dev)를 실행한 후, http://localhost:3000/css-modules로 접속합니다.

  • 두 개의 버튼이 서로 다른 배경색을 가지고 있는 것을 확인할 수 있습니다.
  • 브라우저 개발자 도구(Elements 탭)에서 버튼의 클래스 이름이 원래의 btn, primary와 어떻게 매핑되는지 확인합니다. 구체적인 출력 이름은 실행 환경에 따라 달라질 수 있습니다.
  • page.module.css의 클래스 이름도 유사하게 고유한 이름으로 변환됩니다.

CSS 모듈과 일반 CSS의 차이점

일반 CSS와 CSS 모듈의 적용 범위

일반 CSS와 CSS 모듈의 적용 범위을 비교합니다.

일반 CSS와 CSS 모듈의 적용 범위
비교 기준일반 CSSCSS 모듈
클래스 이름작성한 전역 이름을 여러 파일이 함께 사용파일별 고유 이름으로 변환하여 import 객체로 참조
불러오기App Router의 JS/TS import 또는 스타일시트 연결styles 객체를 import하고 className에 매핑된 이름 지정
적용 범위일치하는 선택자에 따라 여러 화면에 영향생성된 클래스를 적용한 요소에 영향; 상속과 전역 규칙은 남음
공유와 삭제전역 선택자의 사용처 확인같은 모듈을 여러 컴포넌트가 공유할 수 있으므로 import 사용처 확인
클래스 이름
일반 CSS: 작성한 전역 이름을 여러 파일이 함께 사용
CSS 모듈: 파일별 고유 이름으로 변환하여 import 객체로 참조
불러오기
일반 CSS: App Router의 JS/TS import 또는 스타일시트 연결
CSS 모듈: styles 객체를 import하고 className에 매핑된 이름 지정
적용 범위
일반 CSS: 일치하는 선택자에 따라 여러 화면에 영향
CSS 모듈: 생성된 클래스를 적용한 요소에 영향; 상속과 전역 규칙은 남음
공유와 삭제
일반 CSS: 전역 선택자의 사용처 확인
CSS 모듈: 같은 모듈을 여러 컴포넌트가 공유할 수 있으므로 import 사용처 확인

CSS 모듈은 Shadow DOM을 만들지 않습니다. 클래스 이름 지역화와 컴포넌트의 독점 소유는 서로 다른 개념입니다.


SCSS/SASS와 CSS 모듈 함께 사용하기

Next.js는 CSS 모듈과 함께 SCSS/SASS도 기본적으로 지원합니다.

현재는 sass(Dart Sass) 패키지를 설치한 후, 파일 확장자를 .module.scss 또는 .module.sass로 변경하여 사용하면 됩니다.

아래 원문은 darken()을 사용합니다. 이 함수는 Dart Sass에서 폐기 예정이며, 새 코드에서 같은 HSL 명도 감소를 표현하려면 @use "sass:color"와 color.adjust($color, $lightness: -10%, $space: hsl)를 사용할 수 있습니다. 비례 조정인 color.scale()과는 계산이 다릅니다.

SCSS/SASS 설치
npm install sass
# 또는
yarn add sass

파일 이름 변경: StyledButton.module.css를 StyledButton.module.scss로 변경하고, SCSS 문법(중첩, 변수 등)을 사용할 수 있습니다.

src/app/css-modules/StyledButton.module.scss
$primary-color: #28a745;
$default-color: #007bff;

.btn {
  padding: 12px 25px;
  font-size: 1.1em;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transition: background-color 0.3s ease, transform 0.1s ease;
  font-weight: bold;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);

  &:hover { // SCSS 중첩 문법
    transform: translateY(-2px);
  }

  &:active {
    transform: translateY(0);
    box-shadow: none;
  }

  /* 기본 버튼 스타일 */
  background-color: $default-color; // SCSS 변수 사용
  color: white;
}

/* primary prop이 true일 때 적용될 스타일 */
.primary {
  background-color: $primary-color;
  color: white;

  &:hover {
    background-color: darken($primary-color, 10%); // SCSS 함수 사용
  }
}
컴포넌트에서 임포트 경로 변경
src/app/css-modules/StyledButton.tsx (수정)
import buttonStyles from './StyledButton.module.scss'; // 🚨 확장자를 .scss로 변경
// ...

이제 SCSS의 변수, 중첩, 믹스인을 CSS 모듈의 파일 단위 스코프 안에서 사용할 수 있습니다.

상태 관리 도구

이전 페이지

Sass 통합

다음 페이지

이 페이지의 목차

CSS 모듈이란 무엇인가요?왜 CSS 모듈을 사용해야 할까요?Next.js에서 CSS 모듈 사용하기CSS 모듈과 일반 CSS의 차이점SCSS/SASS와 CSS 모듈 함께 사용하기