CSS-in-JS#
- 상위 메뉴: Guides
- 전체 목차: Next.js 학습 문서
학습 목표#
- App Router에서 CSS-in-JS 라이브러리를 쓰기 위한 React와 라이브러리의 조건을 설명할 수 있다.
- 서버 렌더링과 스트리밍 중 스타일 레지스트리가 CSS를 수집하고 삽입하는 흐름을 설명할 수 있다.
styled-jsx와styled-components를 루트 레이아웃에 연결할 수 있다.
핵심 개념 및 설명#
경고: CSS-in-JS를 Server Component와 스트리밍 같은 최신 React 기능과 함께 쓰려면, 라이브러리 작성자가 동시성 렌더링(concurrent rendering)을 포함한 최신 React를 지원해야 한다.
app 디렉토리의 Client Component에서는 다음 라이브러리를 사용할 수 있다.
- `ant-design`
- `chakra-ui`
- `@fluentui/react-components`
- `kuma-ui`
- `@mui/material`
- `@mui/joy`
- `pandacss`
- `styled-jsx`
- `styled-components`
- `stylex`
- `tamagui`
- `tss-react`
- `vanilla-extract`
`emotion`은 현재 App Router 지원을 진행하고 있다.
알아두면 좋은 점: Next.js 팀은 여러 CSS-in-JS 라이브러리를 시험하고 있다. React 18 기능이나 app 디렉토리를 지원하는 라이브러리의 예제를 계속 추가할 예정이다.app에서 CSS-in-JS 구성하기#
CSS-in-JS는 다음 세 요소를 직접 구성해야 하는 opt-in 방식이다.
- 한 번의 렌더링에서 생성된 CSS 규칙을 모으는 스타일 레지스트리를 만든다.
useServerInsertedHTML로 해당 규칙을 사용하는 콘텐츠보다 먼저 스타일을 삽입한다.- 초기 서버 렌더링 동안 애플리케이션을 스타일 레지스트리로 감싸는 Client Component를 만든다.
styled-jsx#
Client Component에서 styled-jsx를 쓰려면 v5.1.0을 사용해야 한다. 레지스트리는 최초 렌더링에서 한 번만 만들고, 서버가 HTML을 만들 때 수집한 스타일을 반환한 뒤 비운다.
// app/registry.tsx
'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
}) {
// 지연 초기화로 스타일시트를 한 번만 만든다.
const [registry] = useState(() => createStyleRegistry())
useServerInsertedHTML(() => {
const styles = registry.styles()
registry.flush()
return <>{styles}</>
})
return <StyleRegistry registry={registry}>{children}</StyleRegistry>
}그다음 루트 레이아웃을 레지스트리로 감싼다.
// app/layout.tsx
import StyledJsxRegistry from './registry'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<StyledJsxRegistry>{children}</StyledJsxRegistry>
</body>
</html>
)
}공식 저장소의 `with-styled-jsx` 예제에서 전체 구성을 확인할 수 있다.
Styled Components#
다음 구성은 styled-components@6 이상을 기준으로 한다. 먼저 Next.js 컴파일러의 styledComponents 지원을 켠다.
// next.config.js
module.exports = {
compiler: {
styledComponents: true,
},
}전역 레지스트리는 렌더링 중 만들어진 규칙을 모으고, useServerInsertedHTML은 루트 레이아웃의 <head>에 해당 규칙을 삽입한다. 브라우저에서는 styled-components가 평소처럼 동작하므로 레지스트리 래퍼 없이 자식을 반환한다.
// lib/registry.tsx
'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
}) {
// 서버 렌더링에 사용할 스타일시트를 한 번만 만든다.
const [sheet] = useState(() => new ServerStyleSheet())
useServerInsertedHTML(() => {
const styles = sheet.getStyleElement()
sheet.instance.clearTag()
return <>{styles}</>
})
if (typeof window !== 'undefined') return <>{children}</>
return <StyleSheetManager sheet={sheet.instance}>{children}</StyleSheetManager>
}// app/layout.tsx
import StyledComponentsRegistry from './lib/registry'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
</body>
</html>
)
}공식 저장소의 `with-styled-components` 예제도 참고할 수 있다.
알아두면 좋은 점:
- 서버 렌더링 중에는 스타일을 전역 레지스트리로 추출한 뒤 HTML의 <head>로 내보낸다. 스타일 규칙이 그 규칙을 사용하는 콘텐츠보다 먼저 놓이게 된다. 향후에는 React의 새 기능으로 삽입 위치를 결정할 수도 있다.- 스트리밍 중에는 각 청크의 스타일을 수집해 기존 스타일 뒤에 추가한다. 클라이언트hydration이 끝나면styled-components가 이후의 다이나믹 스타일을 삽입한다.
- 트리 최상단의 Client Component에서 스타일을 모으면 이후 서버 렌더링 때 스타일을 다시 만들지 않고 Server Component payload에도 보내지 않으므로 더 효율적이다.- 개별 컴파일 속성을 조정하는 고급 사용법은 Next.js 컴파일러의 styled-components 설정을 참고한다.
예제 및 데모 설계#
- Phase 2에서 같은 카드 UI를
styled-jsx와styled-components로 각각 구성한다. - 초기 문서 응답의
<head>를 관찰해 콘텐츠보다 스타일 태그가 먼저 삽입되는지 확인한다. - 스트리밍되는 영역을 추가하고, 청크별 스타일이 기존 스타일 뒤에 합쳐진 뒤
hydration후 클라이언트가 다이나믹 스타일을 관리하는 과정을 비교한다. - 현재 Phase 1에서는 구현하지 않고 위 관찰 항목과 파일 구성을 설계로만 남긴다.
연습 문제#
- App Router의 CSS-in-JS 구성에서 렌더링 중 생성된 CSS 규칙을 모으는 요소는 무엇인가?
- Route Handler
- 스타일 레지스트리
postcss.config.js- Server Component
payload
정답 보기
정답: 2 — 스타일 레지스트리가 한 번의 렌더링에서 생성된 CSS 규칙을 수집한다.
useServerInsertedHTML의 역할로 맞는 것은 무엇인가?
- 브라우저에서 CSS 파일을 지연 다운로드한다.
- 모든 Server Component를 Client Component로 바꾼다.
- 수집한 스타일을 그 스타일을 사용할 콘텐츠보다 먼저 삽입한다.
- CSS 클래스 이름을 자동 생성한다.
정답 보기
정답: 3 — 이 훅은 서버가 만든 HTML에 레지스트리의 스타일을 적절한 시점에 삽입한다.
- Styled Components의 서버 렌더링 설명으로 맞는 것을 모두 고르시오.
styled-components@6이상을 기준으로 한다.- 브라우저에서도 서버용
ServerStyleSheet가 계속 자식을 감싼다. - 스트리밍되는 각 청크의 스타일은 기존 스타일에 추가된다.
- 최상단 Client Component 레지스트리는 CSS를 Server Component
payload에 넣기 위한 것이다.
정답 보기
정답: 1, 3 — 브라우저에서는 styled-components가 이어받고, 최상단 레지스트리는 CSS 재생성과 payload 전송을 피한다.
챕터 요약#
- CSS-in-JS 라이브러리는 최신 React의 Server Component, 스트리밍, 동시성 렌더링을 지원해야 한다.
- App Router 구성에는 스타일 레지스트리,
useServerInsertedHTML, 최상단 Client Component가 필요하다. styled-jsx는v5.1.0,styled-components는v6이상을 기준으로 한다.- 서버 렌더링에서는 스타일을 콘텐츠보다 먼저 삽입하고 스트리밍 중에는 청크별 스타일을 이어 붙인다.
- 클라이언트
hydration이 끝나면 CSS-in-JS 라이브러리가 이후의 다이나믹 스타일을 관리한다.