<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>CSS 스타일링 — 데이터스케쳐스 위키</title>
  <meta name="description" content="CSS 스타일링 Next.js는 애플리케이션을 스타일링하는 여러 방법을 제공해요. Tailwind CSS, CSS Modules, 전역 CSS, 외부 스타일시트, 그리고 Sass·CSS in JS 같은 고급 옵션까지요. 각 방식을 언제 쓰는지, CSS 순서가 어떻게 결" />
  
  <link rel="canonical" href="https://data-sketchers.com/wiki/tech-stack.frontend.nextjs.css" />
  

  <!-- Hreflang (KO/EN + x-default) — 대안 언어는 koUrl/enUrl(없으면 생략) -->
  <link rel="alternate" hreflang="ko" href="https://data-sketchers.com/wiki/tech-stack.frontend.nextjs.css" />
  <link rel="alternate" hreflang="en" href="https://data-sketchers.com/en/wiki/tech-stack.frontend.nextjs.css" />
  <link rel="alternate" hreflang="x-default" href="https://data-sketchers.com/wiki/tech-stack.frontend.nextjs.css" />

  <!-- Open Graph -->
  <meta property="og:type" content="website" />
  <meta property="og:site_name" content="DataSketchers" />
  <meta property="og:title" content="CSS 스타일링 — 데이터스케쳐스 위키" />
  <meta property="og:description" content="CSS 스타일링 Next.js는 애플리케이션을 스타일링하는 여러 방법을 제공해요. Tailwind CSS, CSS Modules, 전역 CSS, 외부 스타일시트, 그리고 Sass·CSS in JS 같은 고급 옵션까지요. 각 방식을 언제 쓰는지, CSS 순서가 어떻게 결" />
  <meta property="og:url" content="https://data-sketchers.com/wiki/tech-stack.frontend.nextjs.css" />
  <meta property="og:image" content="https://data-sketchers.com/assets/logo-black.png" />
  <meta property="og:image:width" content="2172" />
  <meta property="og:image:height" content="724" />
  <meta property="og:image:alt" content="CSS 스타일링 — 데이터스케쳐스 위키" />
  <meta property="og:locale" content="ko_KR" />
  <meta property="og:locale:alternate" content="en_US" />

  <!-- Twitter -->
  <meta name="twitter:card" content="summary_large_image" />
  <meta name="twitter:title" content="CSS 스타일링 — 데이터스케쳐스 위키" />
  <meta name="twitter:description" content="CSS 스타일링 Next.js는 애플리케이션을 스타일링하는 여러 방법을 제공해요. Tailwind CSS, CSS Modules, 전역 CSS, 외부 스타일시트, 그리고 Sass·CSS in JS 같은 고급 옵션까지요. 각 방식을 언제 쓰는지, CSS 순서가 어떻게 결" />
  <meta name="twitter:image" content="https://data-sketchers.com/assets/logo-black.png" />
  <meta name="twitter:image:alt" content="CSS 스타일링 — 데이터스케쳐스 위키" />

  <!-- Organization JSON-LD — th:inline 금지(파싱 오류): 사이트 url 을 컨트롤러가 organizationJson 으로 주입.
       주입이 없으면 고정 생략(페이지별 통합 시 site/json 주입). -->
  <script type="application/ld+json">{"@context":"https://schema.org","@type":"Organization","name":"DataSketchers","url":"https://data-sketchers.com","logo":"https://data-sketchers.com/assets/logo-black.png","sameAs":["https://github.com/data-sketchers"]}</script>
  <!-- 페이지별 커스텀 JSON-LD (BlogPosting/Article/BreadcrumbList) — 호출측에서 pageJson 통째로 주입 -->
  <script type="application/ld+json">{"@context":"https://schema.org","@graph":[{"@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","position":"1","name":"Home","item":"jdbc:postgresql://pg-dev.data-sketchers.dev:23452/data-sketchers-wiki?user=data-sketchers-land&password=OZcV%2Brr7rTR7vVJgDFkq6gFsZcBXyoKb1bBsEDIJw12%2BC6uo&sslmode=prefer/wiki/"},{"@type":"ListItem","position":"2","name":"tech-stack.frontend.nextjs.css","item":"jdbc:postgresql://pg-dev.data-sketchers.dev:23452/data-sketchers-wiki?user=data-sketchers-land&password=OZcV%2Brr7rTR7vVJgDFkq6gFsZcBXyoKb1bBsEDIJw12%2BC6uo&sslmode=prefer/wiki/tech-stack.frontend.nextjs.css"}]},{"@type":"Article","headline":"CSS 스타일링","description":"CSS 스타일링 Next.js는 애플리케이션을 스타일링하는 여러 방법을 제공해요. Tailwind CSS, CSS Modules, 전역 CSS, 외부 스타일시트, 그리고 Sass·CSS in JS 같은 고급 옵션까지요. 각 방식을 언제 쓰는지, CSS 순서가 어떻게 결","mainEntityOfPage":"jdbc:postgresql://pg-dev.data-sketchers.dev:23452/data-sketchers-wiki?user=data-sketchers-land&password=OZcV%2Brr7rTR7vVJgDFkq6gFsZcBXyoKb1bBsEDIJw12%2BC6uo&sslmode=prefer/wiki/tech-stack.frontend.nextjs.css","url":"jdbc:postgresql://pg-dev.data-sketchers.dev:23452/data-sketchers-wiki?user=data-sketchers-land&password=OZcV%2Brr7rTR7vVJgDFkq6gFsZcBXyoKb1bBsEDIJw12%2BC6uo&sslmode=prefer/wiki/tech-stack.frontend.nextjs.css","dateModified":"2026-09-12T23:08:28.268719Z"}]}</script>

  <link rel="icon" type="image/png" href="/assets/favicon.png" />
  <link rel="preconnect" href="https://cdn.jsdelivr.net" />
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/variable/pretendardvariable-dynamic-subset.min.css" />
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500&display=swap" />
  <link rel="stylesheet" href="/ds/tokens.css" />
  <!-- 원본 디자인 시스템 재현 (기존 대비 95%+ 목표) -->
  <link rel="stylesheet" href="/ds/base.css" />
  <link rel="stylesheet" href="/ds/components.css" />
  <link rel="stylesheet" href="/ds/post.css" />
  <link rel="stylesheet" href="/ds/effects.css" />
  <script src="/ds/effects.js" defer></script>
  <script src="/ds/threads.js" defer></script>
</head>
<body>
  <html>
<header id="site-header" class="site-header">
  <div class="site-header__inner">
    <a href="/" class="site-header__logo" aria-label="DataSketchers 홈" data-logo-host>
      <img class="site-header__logo-word" src="/assets/logo-data-sketchers-light-bg.png" alt="" width="153" height="51" style="height:48px;width:auto" />
    </a>
    <nav class="site-header__nav" aria-label="주 메뉴">
      <!-- DB 관리 헤더 메뉴 (site_menu source-of-truth). 항목이 children 을 가지면 서브메뉴 렌더 -->
      <div class="site-header__nav-wrap">
        
        
          <a href="/about" class="site-header__nav-item">회사소개</a>
        
      </div>
      <div class="site-header__nav-wrap">
        
        
          <a href="/blog" class="site-header__nav-item">블로그</a>
        
      </div>
    </nav>
    <div class="site-header__utils">
      <button class="site-header__menu-btn" data-menu-open aria-label="전체 메뉴 열기" aria-expanded="false">
        <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round">
          <line x1="4" y1="8" x2="20" y2="8" /><line x1="4" y1="16" x2="20" y2="16" />
        </svg>
      </button>
    </div>
  </div>
</header>

<!-- 전체 메뉴(햄버거) 풀 스크린 오버레이 — 데스크톱·모바일 모두 동작. hamburger_menu (DB) 기반 -->
<div class="site-menu-overlay" data-menu-overlay aria-hidden="true">
  <div class="site-menu-overlay__scrim" data-menu-close></div>
  <div class="site-menu-overlay__panel" role="dialog" aria-modal="true" aria-label="전체 메뉴">
    <div class="site-menu-overlay__head">
      <a href="/" class="site-menu-overlay__logo" data-menu-close>
        <img src="/assets/logo-data-sketchers-light-bg.png" alt="DataSketchers" width="132" height="44" style="height:36px;width:auto" />
      </a>
      <button class="site-menu-overlay__close" data-menu-close aria-label="전체 메뉴 닫기">
        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="18" y1="6" x2="6" y2="18" /><line x1="6" y1="6" x2="18" y2="18" /></svg>
      </button>
    </div>
    <nav class="site-menu-overlay__nav" aria-label="전체 메뉴 목록">
      <h2 class="site-menu-overlay__title">전체 메뉴</h2>
      <div class="site-menu-overlay__group">
        <a href="/about" class="site-menu-overlay__group-title"
           data-menu-close>회사소개</a>
        
      </div>
      <div class="site-menu-overlay__group">
        <a href="/services" class="site-menu-overlay__group-title"
           data-menu-close>사업영역</a>
        
      </div>
      <div class="site-menu-overlay__group">
        <a href="/products" class="site-menu-overlay__group-title"
           data-menu-close>D-SKET</a>
        <div class="site-menu-overlay__subs">
          <a href="/products/webbuilder"
             class="site-menu-overlay__sub" data-menu-close>웹빌더</a><a href="/products/canvas"
             class="site-menu-overlay__sub" data-menu-close>Canvas</a><a href="/products/events"
             class="site-menu-overlay__sub" data-menu-close>Events</a>
        </div>
      </div>
      <div class="site-menu-overlay__group">
        <a href="/clients" class="site-menu-overlay__group-title"
           data-menu-close>고객사</a>
        
      </div>
      <div class="site-menu-overlay__group">
        <a href="/blog" class="site-menu-overlay__group-title"
           data-menu-close>블로그</a>
        
      </div>
      <div class="site-menu-overlay__group">
        <a href="/" class="site-menu-overlay__group-title"
           data-menu-close>위키</a>
        
      </div>
      <div class="site-menu-overlay__group">
        <a href="/press" class="site-menu-overlay__group-title"
           data-menu-close>보도자료</a>
        
      </div>
      <div class="site-menu-overlay__group">
        <a href="/esg" class="site-menu-overlay__group-title"
           data-menu-close>ESG</a>
        
      </div>
    </nav>
  </div>
</div>
</html>

  <main class="ds-container" style="padding-top:calc(var(--header-h) + 24px);">
    <div class="wiki-layout" style="padding-top:0;">   <!-- layout CSS(columns/gap) 는 ds/effects.css 의 .wiki-layout 가 담당 -->

      <!-- ═══ 왼쪽: scope tree (재귀, 접기/펼치기) ═══ -->
      <aside id="wiki-scope" class="wiki-scope">
        <div style="padding:12px 12px 10px; font-family:var(--font-mono); font-size:11px; letter-spacing:.12em; text-transform:uppercase; color:var(--ds-text-3);">Topics</div>
        <!-- 조상 breadcrumb (root=현재 페이지이므로 위로 이동 경로) -->
        <nav aria-label="위키 경로" style="display:flex; flex-wrap:wrap; gap:4px 2px; align-items:center; padding:0 12px 10px; border-bottom:1px solid var(--ds-line); margin-bottom:8px;">
          <a href="/wiki/tech-stack" style="font-size:11px; color:var(--ds-text-3); text-decoration:none; white-space:nowrap;">기술 스택</a><a href="/wiki/tech-stack.frontend" style="font-size:11px; color:var(--ds-text-3); text-decoration:none; white-space:nowrap;">프론트엔드</a><a href="/wiki/tech-stack.frontend.nextjs" style="font-size:11px; color:var(--ds-text-3); text-decoration:none; white-space:nowrap;">Next.js</a>
          <span style="color:var(--ds-line-strong); font-size:10px;">›</span><span style="color:var(--ds-line-strong); font-size:10px;">›</span><span style="color:var(--ds-line-strong); font-size:10px;">›</span>
        </nav>
        <nav aria-label="위키 주제 트리" style="display:flex; flex-direction:column; gap:2px;">
          <ul class="wiki-tree" style="list-style:none; margin:0; padding:0;">
    
      <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      <button type="button" class="wiki-tree__toggle" data-wtree-toggle
              aria-label="하위 토글" aria-expanded="true">▾</button>
      

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.frontend.nextjs">Next.js</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    <ul class="wiki-tree"
        style="list-style:none; margin:0; padding:0 0 0 14px;">
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.frontend.nextjs.caching">캐싱(Caching)</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node is-active" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link is-active"
         href="/wiki/tech-stack.frontend.nextjs.css">CSS 스타일링</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.frontend.nextjs.deploying">배포(Deploying)</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.frontend.nextjs.error-handling">오류 처리(Error Handling)</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.frontend.nextjs.fetching-data">데이터 페칭</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.frontend.nextjs.layouts-and-pages">레이아웃과 페이지</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.frontend.nextjs.linking-and-navigating">링크와 네비게이션</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.frontend.nextjs.mutating-data">데이터 변경</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.frontend.nextjs.project-structure">프로젝트 구조와 구성</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.frontend.nextjs.revalidating">재검증(Revalidating)</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.frontend.nextjs.route-handlers">Route Handlers</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
        <li class="wiki-tree__node" style="margin:0; padding:0;">

    <div class="wiki-tree__row">
      <!-- 접기/펼치기 토글: 자식 보유 노드만. 클릭 = 하위 토글(페이지 이동 없음) -->
      
      <span class="wiki-tree__dot" aria-hidden="true"></span>

      <!-- 제목: 문서 링크 또는 목차 앵커.
           th:if / th:else 는 이 템플릿에서 인접 정합이 깨져 둘 다 렌더되는 버그가 있음(중복 2열 노출).
           반드시 버튼/점 처럼 상호배타 th:if 두 개로 쓴다. -->
      
      <a class="wiki-tree__link"
         href="/wiki/tech-stack.frontend.nextjs.server-and-client-components">서버 컴포넌트와 클라이언트 컴포넌트</a>
    </div>

    <!-- 하위 트리: 펼침(isOpen)일 때만 재귀 렌더. 접힌 노드는 빈 placeholder `<ul>` 만 두고
         (data-wtree-lazy = 지연 로드 경로) 펼칠 때 /wiki/tree/{path} 프래그먼트로 로드.
         hidden 속성 금지 — hidden=false 가 요소를 숨김. CSS is-collapsed 로 숨김. -->
    
  </li>
      
    </ul>
  </li>
    
  </ul>
        </nav>
      </aside>

      <!-- ═══ 중앙: 콘텐츠 ═══ -->
      <article class="ds-doc" style="min-width:0;">
        <nav class="ds-breadcrumb" aria-label="breadcrumb" style="padding:12px 0 16px; font-size:12.5px; color:var(--ds-text-3);">
          <a href="/wiki/tech-stack" style="color:inherit; text-decoration:none;">기술 스택</a><a href="/wiki/tech-stack.frontend" style="color:inherit; text-decoration:none;">프론트엔드</a><a href="/wiki/tech-stack.frontend.nextjs" style="color:inherit; text-decoration:none;">Next.js</a>
          <span style="color:var(--ds-line-strong);"> › </span><span style="color:var(--ds-line-strong);"> › </span><span style="color:var(--ds-line-strong);"> › </span>
        </nav>
        <h1 style="margin-bottom:20px;">CSS 스타일링</h1>
        <!-- 모바일 전용: 목차를 본문 상단(제목 하단)에 항상 펼침 -->
        <nav aria-label="페이지 목차" class="wiki-toc-mobile"
             style="display:none; border-bottom:1px solid var(--ds-line); padding-bottom:12px; margin-bottom:24px;">
          <div class="ds-label" style="font-size:11px; letter-spacing:.12em; text-transform:uppercase; color:var(--ds-text-3); margin-bottom:8px;">목차</div>
          <div style="display:flex; flex-direction:column; gap:2px;">
            <a href="#css-스타일링" style="padding-left:0px">CSS 스타일링</a><a href="#tailwind-css" style="padding-left:0px">Tailwind CSS</a><a href="#css-modules" style="padding-left:0px">CSS Modules</a><a href="#전역-cssglobal-css" style="padding-left:0px">전역 CSS(Global CSS)</a><a href="#외부-스타일시트" style="padding-left:0px">외부 스타일시트</a><a href="#css-순서와-병합" style="padding-left:0px">CSS 순서와 병합</a><a href="#개발-vs-프로덕션" style="padding-left:0px">개발 vs 프로덕션</a><a href="#더-알아보기" style="padding-left:0px">더 알아보기</a>
          </div>
        </nav>
        <div><h2 id="css-스타일링">CSS 스타일링</h2>
<p>Next.js는 애플리케이션을 스타일링하는 여러 방법을 제공해요. Tailwind CSS, CSS Modules, 전역 CSS, 외부 스타일시트, 그리고 Sass·CSS-in-JS 같은 고급 옵션까지요. 각 방식을 언제 쓰는지, CSS 순서가 어떻게 결정되는지, 개발·프로덕션에서 어떻게 다른지를 정리할게요.</p>
<blockquote>
<p>출처: <a href="https://nextjs.org/docs/app/getting-started/css">https://nextjs.org/docs/app/getting-started/css</a></p>
</blockquote>
<h2 id="tailwind-css">Tailwind CSS</h2>
<p>Tailwind CSS는 유틸리티 우선(utility-first) CSS 프레임워크예요. 낮은 수준의 유틸리티 클래스를 제공해 커스텀 디자인을 빠르게 만들 수 있어요.</p>
<p><code>postcss.config.mjs</code>에 PostCSS 플러그인을 추가하고, 전역 CSS 파일에서 Tailwind를 import한 뒤 루트 레이아웃에서 그 CSS 파일을 import하면 돼요.</p>
<pre><code class="language-tsx">import './globals.css'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    &lt;html lang=&quot;en&quot;&gt;
      &lt;body&gt;{children}&lt;/body&gt;
    &lt;/html&gt;
  )
}
</code></pre>
<p>이제 Tailwind의 유틸리티 클래스를 사용할 수 있어요.</p>
<pre><code class="language-tsx">export default function Page() {
  return (
    &lt;main className=&quot;flex min-h-screen flex-col items-center justify-between p-24&quot;&gt;
      &lt;h1 className=&quot;text-4xl font-bold&quot;&gt;Welcome to Next.js!&lt;/h1&gt;
    &lt;/main&gt;
  )
}
</code></pre>
<h2 id="css-modules">CSS Modules</h2>
<p>CSS Modules는 클래스 이름을 고유하게 생성해 CSS를 <strong>로컬 스코프</strong>로 만들어요. 그래서 같은 클래스 이름을 서로 다른 파일에서 써도 이름 충돌을 걱정할 필요가 없어요.</p>
<p><code>.module.css</code> 확장자 파일을 만들고 <code>app</code> 디렉토리의 어떤 컴포넌트든 import하면 돼요.</p>
<pre><code class="language-css">/* app/blog/blog.module.css */
.blog {
  padding: 24px;
}
</code></pre>
<pre><code class="language-tsx">// app/blog/page.tsx
import styles from './blog.module.css'

export default function Page() {
  return &lt;main className={styles.blog}&gt;&lt;/main&gt;
}
</code></pre>
<h2 id="전역-cssglobal-css">전역 CSS(Global CSS)</h2>
<p>전역 CSS를 사용하면 애플리케이션 전체에 스타일을 적용할 수 있어요. <code>app/global.css</code> 파일을 만들고 루트 레이아웃에서 import하면 <strong>모든 라우트</strong>에 적용돼요.</p>
<p>전역 스타일은 <code>app</code> 디렉토리 안의 어떤 레이아웃·페이지·컴포넌트에서든 import할 수 있어요. 하지만 Next.js는 React의 스타일시트 내장 지원을 Suspense와 통합해서 쓰기 때문에, 라우트를 이동해도 스타일시트를 제거하지 않아 충돌이 생길 수 있어요. 그래서 전역 스타일은 <strong>진짜 전역 CSS</strong>(Tailwind의 베이스 스타일 등)에 쓰고, 컴포넌트 스타일은 Tailwind로, 커스텀 스코프 CSS가 필요할 때만 CSS Modules를 쓰는 걸 권장해요.</p>
<h2 id="외부-스타일시트">외부 스타일시트</h2>
<p>외부 패키지가 배포한 스타일시트는 <code>app</code> 디렉토리 어디서든(colocated 컴포넌트 포함) import할 수 있어요.</p>
<pre><code class="language-tsx">import 'bootstrap/dist/css/bootstrap.css'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    &lt;html lang=&quot;en&quot;&gt;
      &lt;body className=&quot;container&quot;&gt;{children}&lt;/body&gt;
    &lt;/html&gt;
  )
}
</code></pre>
<h2 id="css-순서와-병합">CSS 순서와 병합</h2>
<p>Next.js는 프로덕션 빌드에서 스타일시트를 자동으로 청킹(merge)하며 최적화해요. <strong>CSS 순서는 코드에서 스타일을 import한 순서에 따라 정해져요.</strong> 예측 가능한 순서를 유지하려면:</p>
<ul>
<li>CSS import는 JavaScript/TypeScript 엔트리 파일 하나에 모아두려고 해요.</li>
<li>전역 스타일과 Tailwind 스타일시트는 애플리케이션 루트에서 import해요.</li>
<li>대부분의 스타일링은 Tailwind CSS로 하고, Tailwind 유틸리티로 부족한 컴포넌트 전용 스타일에만 CSS Modules를 써요.</li>
<li>CSS Modules에는 <code>&lt;name&gt;.module.css</code> 같은 일관된 네이밍 규칙을 써요.</li>
<li>공유 스타일은 공유 컴포넌트로 추출해 중복 import를 피해요.</li>
</ul>
<h2 id="개발-vs-프로덕션">개발 vs 프로덕션</h2>
<ul>
<li>개발(<code>next dev</code>)에서는 Fast Refresh로 CSS 업데이트가 즉시 적용돼요.</li>
<li>프로덕션(<code>next build</code>)에서는 모든 CSS 파일이 minified·code-split된 여러 <code>.css</code> 파일로 합쳐져, 라우트에 필요한 최소한의 CSS만 로드돼요.</li>
<li>프로덕션에서는 JavaScript를 꺼도 CSS가 로드되지만, 개발에서는 Fast Refresh 때문에 JavaScript가 필수예요.</li>
<li>CSS 순서는 개발에서 다르게 동작할 수 있으니, 항상 <code>next build</code>로 최종 순서를 확인해야 해요.</li>
</ul>
<h2 id="더-알아보기">더 알아보기</h2>
<ul>
<li>Sass 가이드: <a href="https://nextjs.org/docs/app/guides/sass">https://nextjs.org/docs/app/guides/sass</a></li>
<li>CSS-in-JS 가이드: <a href="https://nextjs.org/docs/app/guides/css-in-js">https://nextjs.org/docs/app/guides/css-in-js</a></li>
<li>커스텀 CSS 청킹: <a href="https://nextjs.org/docs/app/api-reference/config/next-config-js/cssChunking">https://nextjs.org/docs/app/api-reference/config/next-config-js/cssChunking</a></li>
</ul>
</div>
      </article>

      <!-- ═══ 오른쪽: TOC (H2/H3 목차, 접기/펼치기) ═══ -->
      <aside class="wiki-toc" data-toc
             style="position:sticky; top:calc(var(--header-h) + 24px); max-height:calc(100vh - var(--header-h) - 48px); overflow:auto; padding:8px 0; border-left:1px solid var(--ds-line); padding-left:16px;">
        <button type="button" class="wiki-toc__toggle" aria-expanded="true" aria-controls="wiki-toc-nav">
          <span class="wiki-toc__label">목차</span>
          <span class="wiki-toc__chevron">▾</span>
        </button>
        <nav id="wiki-toc-nav" class="wiki-toc__nav" aria-label="페이지 목차">
          <a href="#css-스타일링" style="padding-left:0px">CSS 스타일링</a><a href="#tailwind-css" style="padding-left:0px">Tailwind CSS</a><a href="#css-modules" style="padding-left:0px">CSS Modules</a><a href="#전역-cssglobal-css" style="padding-left:0px">전역 CSS(Global CSS)</a><a href="#외부-스타일시트" style="padding-left:0px">외부 스타일시트</a><a href="#css-순서와-병합" style="padding-left:0px">CSS 순서와 병합</a><a href="#개발-vs-프로덕션" style="padding-left:0px">개발 vs 프로덕션</a><a href="#더-알아보기" style="padding-left:0px">더 알아보기</a>
        </nav>
      </aside>
    </div>
  </main>

  <!-- ═══ scope tree 접기/펼치기 인터랙션 ═══ -->
  <script>
  (function () {
    // 페이지 이동 시 항상 최상단(제목/목차)에서 시작 — 모바일에서 트리가 하단에 있어도 뷰포트를 하단에 남기지 않음
    if ('scrollRestoration' in history) { history.scrollRestoration = 'manual'; }
    window.addEventListener('load', function () { window.scrollTo(0, 0); });

    document.querySelectorAll('[data-wtree-toggle]').forEach(function (btn) {
      btn.addEventListener('click', function (e) {
        e.preventDefault();
        e.stopPropagation();
        var li = btn.closest('.wiki-tree__node');
        if (!li) return;
        var sub = li.querySelector(':scope > .wiki-tree');
        if (!sub) return;
        var lazyPath = sub.getAttribute('data-wtree-lazy');
        if (lazyPath && sub.getAttribute('data-wtree-loaded') !== '1') {
          // 첫 펼침: 하위 트리를 lazy 프래그먼트로 로드 후 펼침
          fetch('/wiki/tree/' + lazyPath.replace(/^\/+/, ''))
            .then(function (r) { return r.text(); })
            .then(function (html) {
              sub.innerHTML = html;
              sub.setAttribute('data-wtree-loaded', '1');
              sub.removeAttribute('data-wtree-lazy');
              wireToggles(sub);
              openSub(sub, btn);
            })
            .catch(function () { openSub(sub, btn); });
          return;
        }
        openSub(sub, btn);
      });
    });

    function openSub(sub, btn) {
      var collapsed = sub.classList.toggle('is-collapsed');
      sub.hidden = collapsed;
      btn.textContent = collapsed ? '▸' : '▾';
      btn.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
    }

    // lazy 프래그먼트로 삽입된 하위 노드들의 토글도 이벤트 연결
    function wireToggles(scope) {
      scope.querySelectorAll('[data-wtree-toggle]').forEach(function (btn) {
        if (btn._wtreeBound) return;
        btn._wtreeBound = true;
        btn.addEventListener('click', function (e) {
          e.preventDefault();
          e.stopPropagation();
          var li = btn.closest('.wiki-tree__node');
          if (!li) return;
          var sub = li.querySelector(':scope > .wiki-tree');
          if (!sub) return;
          var lazyPath = sub.getAttribute('data-wtree-lazy');
          if (lazyPath && sub.getAttribute('data-wtree-loaded') !== '1') {
            fetch('/wiki/tree/' + lazyPath.replace(/^\/+/, ''))
              .then(function (r) { return r.text(); })
              .then(function (html) {
                sub.innerHTML = html;
                sub.setAttribute('data-wtree-loaded', '1');
                sub.removeAttribute('data-wtree-lazy');
                wireToggles(sub);
                openSub(sub, btn);
              })
              .catch(function () { openSub(sub, btn); });
            return;
          }
          openSub(sub, btn);
        });
      });
    }

    // TOC 접기/펼치기 토글
    var toc = document.querySelector('[data-toc]');
    if (toc) {
      var toggleBtn = toc.querySelector('.wiki-toc__toggle');
      var nav = toc.querySelector('.wiki-toc__nav');
      var chevron = toc.querySelector('.wiki-toc__chevron');
      if (toggleBtn && nav) {
        toggleBtn.addEventListener('click', function () {
          var collapsed = toc.classList.toggle('is-collapsed');
          toggleBtn.setAttribute('aria-expanded', collapsed ? 'false' : 'true');
          if (chevron) chevron.textContent = collapsed ? '▸' : '▾';
        });
      }
    }
    // 페이지 진입 시 활성 노드로 스크롤 노출
    var active = document.querySelector('.wiki-tree__node.is-active');
    if (active && active.scrollIntoView) {
      try { active.scrollIntoView({ block: 'nearest' }); } catch (e) {}
    }
  })();
  </script>

  <style>
    /* scope tree 행 스타일: 활성 강조 + 들여쓰기 토글 정렬 */
    .wiki-tree__row { display:flex; align-items:center; gap:6px; border-radius:var(--r-xs); }
    .wiki-tree__row:hover { background:var(--ds-bg-subtle); }
    .wiki-tree__toggle {
      flex:0 0 auto; width:16px; height:22px; padding:0; border:none; background:none;
      cursor:pointer; color:var(--ds-text-3); font-size:10px; line-height:1;
      display:flex; align-items:center; justify-content:center;
    }
    .wiki-tree__dot { flex:0 0 auto; width:16px; height:1px; }
    .wiki-tree__link {
      flex:1 1 auto; min-width:0; padding:6px 6px; border-radius:var(--r-xs);
      font-size:13px; color:var(--ds-text-2); text-decoration:none; line-height:1.35; overflow:hidden; text-overflow:ellipsis;
    }
    .wiki-tree__node.is-active > .wiki-tree__row { background:var(--ds-text); }
    .wiki-tree__node.is-active > .wiki-tree__row .wiki-tree__link { color:var(--ds-bg-card); font-weight:700; }
    .wiki-tree.is-collapsed { display:none; }
    /* TOC 접기/펼치기 토글 버튼 */
    .wiki-toc__toggle {
      display:flex; align-items:center; justify-content:space-between; width:100%;
      padding:4px 6px; margin-bottom:6px; border:none; background:none; cursor:pointer;
      font-size:12px; font-weight:600; letter-spacing:.08em; text-transform:uppercase;
      color:var(--ds-text-2); border-radius:var(--r-xs); text-align:left;
    }
    .wiki-toc__toggle:hover { color:var(--ds-text); background:var(--ds-bg-subtle); }
    .wiki-toc__nav { display:flex; flex-direction:column; gap:2px; }
    .wiki-toc__chevron { font-size:10px; color:var(--ds-text-3); transition:transform .15s var(--ease-out); }
    .wiki-toc.is-collapsed .wiki-toc__chevron { transform:rotate(-90deg); }
    .wiki-toc.is-collapsed .wiki-toc__nav { display:none; }
  </style>

  <html>
<footer class="site-footer--compact" style="background:var(--ds-bg-strong); color:var(--ds-text-on-dark); margin-top:80px;">
  <div class="site-footer--compact__inner">
    <!-- 1줄 블록: 좌측 브랜드 + 우측 링크 가로 나열 -->
    <div style="display:flex; justify-content:space-between; align-items:center; flex-wrap:wrap; gap:20px 32px;">
      <div style="display:flex; align-items:center; gap:16px; flex-wrap:wrap;">
        <img src="/assets/logo-white.png" alt="DataSketchers" style="height:32px; width:auto;" />
        <span style="font-size:13px; color:#b0b0b2; max-width:320px;">AI·데이터·디자인으로 기업의 디지털 전환을 지원합니다. 서울시립대학교 캠퍼스타운 창업기업</span>
      </div>
      <nav style="display:flex; flex-wrap:wrap; gap:8px 24px; font-size:13px;" aria-label="푸터 링크">
        <a href="/about" style="color:#e5e5e6; text-decoration:none;">회사소개</a>
        <a href="/services" style="color:#e5e5e6; text-decoration:none;">사업영역</a>
        <a href="/products" style="color:#e5e5e6; text-decoration:none;">D-SKET</a>
        <a href="/clients" style="color:#e5e5e6; text-decoration:none;">고객사</a>
        <a href="/blog" style="color:#e5e5e6; text-decoration:none;">블로그</a>
        <a href="/" style="color:#e5e5e6; text-decoration:none;">위키</a>
        <a href="/press" style="color:#e5e5e6; text-decoration:none;">보도자료</a>
        <a href="/esg" style="color:#e5e5e6; text-decoration:none;">ESG</a>
        <a href="/contact" style="color:#e5e5e6; text-decoration:none;">문의</a>
      </nav>
    </div>
    <div style="margin-top:28px; padding-top:16px; border-top:1px solid rgba(255,255,255,0.12); display:flex; justify-content:space-between; flex-wrap:wrap; gap:12px; font-size:13px; color:#8a8a8d;">
      <span>© 데이터스케쳐스. All rights reserved.</span>
      <span>사업자등록번호 349-81-03411</span>
    </div>
  </div>
</footer>
</html>
</body>
</html>
