How to use CSS-in-JS libraries

김동현·2026년 3월 5일

next.js 공식문서 번역

목록 보기
44/79

주의사항 (Warning): 서버 컴포넌트(Server Components)나 스트리밍(Streaming) 같은 React의 최신 기능들과 함께 CSS-in-JS를 사용하려면, 해당 라이브러리 제작자가 동시성 렌더링(concurrent rendering)을 포함한 최신 버전의 React를 지원해야만 합니다.

💡 강사의 부연 설명 & 팁: React 18부터 도입된 '동시성 렌더링'이나 Next.js의 '서버 컴포넌트' 환경에서는 UI가 서버에서 조각조각 나뉘어 스트리밍 방식으로 클라이언트에 전달됩니다. 과거의 CSS-in-JS 라이브러리들은 브라우저(클라이언트)가 렌더링될 때 동적으로 <style> 태그를 삽입하는 방식이었기 때문에, 이런 최신 서버 렌더링 환경에서는 화면이 깜빡이거나(FOUC 현상) 스타일이 깨지는 문제가 발생할 수 있어요. 그래서 반드시 최신 React를 완벽히 지원하는 라이브러리를 선택하는 것이 매우 중요합니다!

현재 app 디렉토리 내부의 클라이언트 컴포넌트(Client Components)에서 공식적으로 지원되는 라이브러리들은 다음과 같습니다 (알파벳 순서):

다음 라이브러리는 현재 지원을 위해 개발/작업 중입니다:

💡 강사의 실무 팁:
리스트를 보시면 vanilla-extractpandacss 같은 라이브러리들이 눈에 띄죠? 최근 실무 트렌드는 런타임(브라우저 실행 시점)에 CSS를 생성해서 성능을 깎아먹는 전통적인 방식보다는, 빌드 타임에 CSS를 미리 뽑아내는 Zero-runtime CSS-in-JS 라이브러리들을 선호하는 추세입니다. emotion이 아직 작업 중이라는 점이 아쉽지만, 새로운 프로젝트를 시작하신다면 Zero-runtime 라이브러리를 도입해 보시는 것도 강력히 추천합니다!

알아두면 좋은 점 (Good to know): 저희(Next.js 팀)는 현재 다양한 CSS-in-JS 라이브러리들을 테스트하고 있으며, React 18의 기능들이나 app 디렉토리를 지원하는 라이브러리들에 대한 예제를 앞으로 더 많이 추가할 예정입니다.


app 디렉토리에서 CSS-in-JS 설정하기

app 라우터 환경에서 CSS-in-JS를 설정하는 과정은 사용자가 직접 세팅해야 하는(opt-in) 3단계 프로세스로 이루어져 있습니다:

  1. 렌더링 과정에서 발생하는 모든 CSS 규칙들을 하나로 모아줄 스타일 레지스트리(style registry)를 만듭니다.
  2. 스타일이 필요한 콘텐츠가 화면에 그려지기 전에, 규칙들을 미리 주입해 주는 새로운 훅(Hook)인 useServerInsertedHTML을 사용합니다.
  3. 초기 서버 사이드 렌더링(SSR)이 진행되는 동안, 여러분의 앱 전체를 앞서 만든 스타일 레지스트리로 감싸줄 '클라이언트 컴포넌트(Client Component)'를 만듭니다.

💡 강사의 부연 설명:
"왜 이렇게 복잡하게 설정해야 하죠?"라고 생각하실 수 있어요. 예전 pages 라우터 시절에는 _document.js 파일 하나에서 모든 걸 처리했죠. 하지만 app 라우터는 서버 컴포넌트가 중심이 되기 때문에, 서버에서 렌더링될 때 발생하는 스타일 태그들을 차곡차곡 모아놨다가 클라이언트로 한 번에 깔끔하게 넘겨주는 '바구니(레지스트리)' 역할이 필요해진 거랍니다.

styled-jsx 설정하기

클라이언트 컴포넌트에서 styled-jsx를 사용하시려면 반드시 v5.1.0 버전을 사용해야 합니다.
자, 먼저 새로운 레지스트리 파일을 만들어 볼까요?

'use client'

import React, { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { StyleRegistry, createStyleRegistry } from 'styled-jsx'

export default function StyledJsxRegistry({
  children,
}: {
  children: React.ReactNode
}) {
  // Only create stylesheet once with lazy initial state
  // x-ref: [https://reactjs.org/docs/hooks-reference.html#lazy-initial-state](https://reactjs.org/docs/hooks-reference.html#lazy-initial-state)
  const [jsxStyleRegistry] = useState(() => createStyleRegistry())

  useServerInsertedHTML(() => {
    const styles = jsxStyleRegistry.styles()
    jsxStyleRegistry.flush()
    return <>{styles}</>
  })

  return <StyleRegistry registry={jsxStyleRegistry}>{children}</StyleRegistry>
}
'use client'

import React, { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { StyleRegistry, createStyleRegistry } from 'styled-jsx'

export default function StyledJsxRegistry({ children }) {
  // Only create stylesheet once with lazy initial state
  // x-ref: [https://reactjs.org/docs/hooks-reference.html#lazy-initial-state](https://reactjs.org/docs/hooks-reference.html#lazy-initial-state)
  const [jsxStyleRegistry] = useState(() => createStyleRegistry())

  useServerInsertedHTML(() => {
    const styles = jsxStyleRegistry.styles()
    jsxStyleRegistry.flush()
    return <>{styles}</>
  })

  return <StyleRegistry registry={jsxStyleRegistry}>{children}</StyleRegistry>
}

💡 강사의 부연 설명:
코드 중간에 useState(() => createStyleRegistry()) 보이시죠? 이렇게 함수 형태로 초기값을 넣는 걸 '게으른 초기화(Lazy initialization)'라고 부릅니다. 컴포넌트가 리렌더링 될 때마다 불필요하게 레지스트리가 다시 생성되는 걸 막아주는 리액트의 아주 유용한 패턴이에요. 그리고 flush() 메서드는 모아둔 스타일을 비워주는 역할을 해서 동일한 스타일이 중복으로 들어가는 걸 막아줍니다.

그다음, 방금 만든 레지스트리 컴포넌트를 이용해 최상위 레이아웃(root layout)을 감싸주면 됩니다:

import StyledJsxRegistry from './registry'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <body>
        <StyledJsxRegistry>{children}</StyledJsxRegistry>
      </body>
    </html>
  )
}
import StyledJsxRegistry from './registry'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <StyledJsxRegistry>{children}</StyledJsxRegistry>
      </body>
    </html>
  )
}

여기에서 실제 작동하는 예제 코드를 확인해 보세요.

Styled Components 설정하기

이번에는 실무에서 정말 많이 쓰이는 styled-components (버전 6 이상)를 설정하는 방법입니다.

먼저, next.config.js 파일에서 styled-components 사용을 활성화(true)로 켜주세요.

module.exports = {
  compiler: {
    styledComponents: true,
  },
}

💡 강사의 팁: 예전에는 babel-plugin-styled-components 같은 외부 플러그인을 따로 설치해서 복잡하게 설정해야 했지만, 이제는 Next.js가 자체 내장 컴파일러(SWC)를 통해 이 설정을 지원해 주기 때문에 저렇게 코드 몇 줄만 넣으면 알아서 최적화가 끝납니다. 엄청 편해졌죠!

그다음, styled-components API를 사용해서 서버 렌더링 중 발생하는 모든 CSS 스타일 규칙을 수집할 글로벌 레지스트리 컴포넌트를 만들고, 그 규칙들을 반환해 주는 함수를 작성해야 합니다. 그리고 useServerInsertedHTML 훅을 사용해서 수집된 스타일들을 최상위 레이아웃의 <head> 태그 안에 주입(inject)해 줍니다.

'use client'

import React, { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'

export default function StyledComponentsRegistry({
  children,
}: {
  children: React.ReactNode
}) {
  // Only create stylesheet once with lazy initial state
  // x-ref: [https://reactjs.org/docs/hooks-reference.html#lazy-initial-state](https://reactjs.org/docs/hooks-reference.html#lazy-initial-state)
  const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet())

  useServerInsertedHTML(() => {
    const styles = styledComponentsStyleSheet.getStyleElement()
    styledComponentsStyleSheet.instance.clearTag()
    return <>{styles}</>
  })

  if (typeof window !== 'undefined') return <>{children}</>

  return (
    <StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
      {children}
    </StyleSheetManager>
  )
}
'use client'

import React, { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'

export default function StyledComponentsRegistry({ children }) {
  // Only create stylesheet once with lazy initial state
  // x-ref: [https://reactjs.org/docs/hooks-reference.html#lazy-initial-state](https://reactjs.org/docs/hooks-reference.html#lazy-initial-state)
  const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet())

  useServerInsertedHTML(() => {
    const styles = styledComponentsStyleSheet.getStyleElement()
    styledComponentsStyleSheet.instance.clearTag()
    return <>{styles}</>
  })

  if (typeof window !== 'undefined') return <>{children}</>

  return (
    <StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
      {children}
    </StyleSheetManager>
  )
}

마지막으로 최상위 레이아웃(root layout)의 children을 우리가 만든 스타일 레지스트리 컴포넌트로 감싸줍니다:

import StyledComponentsRegistry from './lib/registry'

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <body>
        <StyledComponentsRegistry>{children}</StyledComponentsRegistry>
      </body>
    </html>
  )
}
import StyledComponentsRegistry from './lib/registry'

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <StyledComponentsRegistry>{children}</StyledComponentsRegistry>
      </body>
    </html>
  )
}

여기에서 실제 작동하는 예제 코드를 확인해 보세요.

반드시 알아두면 좋은 점 (Good to know):

  • 서버 렌더링을 하는 동안, 스타일들은 글로벌 레지스트리로 추출된 다음 여러분의 HTML <head> 태그 안으로 전부 쏟아져 들어갑니다(flushed). 이렇게 하면 해당 스타일을 사용하는 실제 콘텐츠들이 화면에 그려지기 전에 스타일 규칙들이 미리 자리를 잡게 되죠. 참고로, 앞으로는 React의 새로운 기능을 사용해서 이 스타일들을 정확히 어디에 주입할지 결정하는 방식으로 바뀔 수도 있습니다.
  • 스트리밍(streaming) 과정 중에는, 데이터를 쪼개서 가져오는 각 청크(chunk) 단위마다 스타일을 수집해서 기존 스타일에 차곡차곡 이어 붙이게 됩니다. 이후 클라이언트 측에서 하이드레이션(hydration)이 완료되고 나면, 평소처럼 styled-components가 주도권을 넘겨받아 동적인 스타일들을 알아서 주입하게 됩니다.
  • 💡 (강사 부연: '하이드레이션'이란 서버에서 만들어진 뼈대 HTML에 리액트가 생명(이벤트 리스너, 상태 등)을 불어넣는 과정을 말해요!)
  • 저희가 굳이 스타일 레지스트리를 트리 최상단(top level)에서 클라이언트 컴포넌트('use client')로 사용하는 이유가 있습니다. 이렇게 하는 것이 CSS 규칙들을 추출해 내는 데 훨씬 효율적이기 때문이에요. 이 방식을 쓰면 다음번 서버 렌더링 때 스타일을 불필요하게 다시 생성하는 걸 막아주고, 서버 컴포넌트의 페이로드(전송 데이터 크기)에 스타일 데이터가 섞여 들어가는 것도 방지할 수 있습니다.
  • styled-components를 컴파일할 때 개별적인 속성들을 세밀하게 설정해야 하는 고급 사용자 분들은, Next.js styled-components API 레퍼런스 문서를 통해 더 자세한 내용을 확인하실 수 있습니다.

모든 문서에 대한 의미론적 개요(semantic overview)를 보시려면 https://nextjs.org/docs/sitemap.md 를 참고해 주세요.

사용 가능한 전체 문서의 색인(index)을 확인하시려면 https://nextjs.org/docs/llms.txt 를 참고해 주세요.

profile
프론트에_가까운_풀스택_개발자

0개의 댓글