Next.js App Router 가이드 정리: CSS-in-JS
1. 개요
- 이 문서는 Next.js App Router(
app디렉터리) 환경에서 CSS-in-JS 라이브러리를 사용하는 방법을 설명한다. - React 18의 Concurrent Rendering, Streaming 과 같은 새로운 기능과 함께 사용하려면, 해당 CSS-in-JS 라이브러리가 최신 React 버전을 지원해야 한다는 점을 경고하고 있다.
- 문서에서 소개하는 라이브러리들은 모두 Client Component에서 사용 가능 한 것을 전제로 한다.
2. 지원되는 CSS-in-JS 라이브러리
문서가 명시적으로 지원한다고 밝힌 라이브러리(알파벳 순)는 다음과 같다.
- 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 은 지원을 개발 중인 라이브러리로 언급된다.
문서는 향후 React 18 및
app디렉터리를 지원하는 CSS-in-JS 라이브러리에 대해 더 많은 예시를 추가할 계획이라고 안내한다.
3. App에서 CSS-in-JS를 설정하는 공통 3단계
Next.js App Router에서 CSS-in-JS를 제대로 SSR/Streaming과 함께 사용하려면, 다음 3단계 opt‑in 구조를 따라야 한다.
-
스타일 레지스트리(Style Registry)
- 한 번의 렌더링에서 생성되는 모든 CSS 규칙을 수집/보관하는 레지스트리를 만든다.
- 보통 Client Component로 구현한다.
-
useServerInsertedHTML훅 사용next/navigation에서 제공하는useServerInsertedHTML훅을 사용하여, 수집한 스타일을 실제 HTML 콘텐츠가 렌더링되기 전에<head>에 주입 한다.- 이렇게 해야 스타일이 내용보다 먼저 로딩되어 FOUC(unstyled content)가 줄어든다.
-
Root Layout에서 레지스트리로 앱 감싸기
app/layout.tsx(Root Layout)에서 스타일 레지스트리 컴포넌트로 전체 앱 트리를 감싼다.- 초기 서버 렌더링 시 스타일이 올바르게 삽입되고, 이후 클라이언트 하이드레이션 이후에는 라이브러리가 평소처럼 동작하도록 하기 위함이다.
이 3단계 패턴은 styled-jsx, styled-components 모두에서 동일하게 사용된다.
4. styled-jsx 설정 방법
4.1 styled-jsx 버전 요구사항
- App Router의 Client Component에서
styled-jsx를 사용하려면 v5.1.0 이상을 사용해야 한다고 문서에서 명시한다.
4.2 스타일 레지스트리 컴포넌트 만들기
app/registry.tsx같은 파일을 만들고, 다음과 같은 역할을 하는 Client Component를 작성한다.createStyleRegistry()로 스타일 레지스트리를 생성한다.- React
useState의 lazy initial state 기능을 사용하여 한 번만 레지스트리를 만들고 재사용 한다. useServerInsertedHTML훅 내부에서:registry.styles()로 현재까지 수집된 스타일을 가져온 뒤,registry.flush()를 호출하여 레지스트리를 비우고,- 반환된 스타일을 JSX로 감싸 반환한다.
- 컴포넌트는
<StyleRegistry>로 children을 감싸서 렌더링한다.
이 구조를 통해 서버 렌더링 시점에 수집된 모든 styled-jsx 스타일이 <head> 앞부분에 삽입된다.
4.3 Root Layout에서 사용하기
app/layout.tsx에서 위에서 만든StyledJsxRegistry(예시 이름)를 import 한다.- Root Layout의
<body>안에서children을StyledJsxRegistry로 감싸면, 전체 앱에서 생성되는styled-jsx스타일이 모두 레지스트리를 통해 관리된다.
// 개념 예시
import StyledJsxRegistry from './registry'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<StyledJsxRegistry>{children}</StyledJsxRegistry>
</body>
</html>
)
}
5. styled-components 설정 방법
이 섹션은 styled-components@6 이상을 기준으로 한다.
5.1 next.config.js 설정
- 먼저
next.config.js에서compiler.styledComponents옵션을 활성화한다.
// next.config.js (개념 예시)
module.exports = {
compiler: {
styledComponents: true,
},
}
이 설정을 통해 Next.js는 styled-components용 트랜스파일 과정을 적용한다.
5.2 styled-components용 레지스트리 컴포넌트 만들기
- 예:
lib/registry.tsx파일에StyledComponentsRegistry컴포넌트를 구현한다. - 주요 역할:
new ServerStyleSheet()로 서버용 스타일 시트를 생성한다.useState의 lazy initial state를 이용해 한 번만 스타일 시트를 만들고 재사용한다.useServerInsertedHTML훅 안에서:getStyleElement()로 현재까지 수집된 스타일 엘리먼트를 가져온다.instance.clearTag()로 내부 스타일 태그를 초기화한다.- JSX로 스타일 엘리먼트를 반환하여
<head>에 삽입되도록 한다.
- 브라우저 환경(
typeof window !== 'undefined')에서는 서버 스타일 시트 없이 그대로children만 반환한다. - 서버 환경에서는
<StyleSheetManager sheet={styledComponentsStyleSheet.instance}>로 children을 감싸서, styled-components가 해당 시트를 사용하도록 한다.
이 구조를 통해, 서버 렌더링 시 수집된 styled-components 스타일이 <head>에 삽입되고, 스트리밍 시 각 청크의 스타일이 기존 스타일에 붙는다.
5.3 Root Layout에서 레지스트리 사용
app/layout.tsx에서StyledComponentsRegistry를 import 하고, Root Layout의<body>안에서children을 감싼다.
import StyledComponentsRegistry from './lib/registry'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
</body>
</html>
)
}
5.4 동작 방식에 대한 메모
styled-components 동작 특성은 다음과 같다.
- 서버 렌더링 동안
- 스타일은 전역 레지스트리에 누적되고,
<head>로 플러시(flush) 된다. - 스타일은 실제 콘텐츠보다 먼저 삽입되어, 스타일이 적용되지 않은 상태로 보이는 시간을 줄인다.
- 스타일은 전역 레지스트리에 누적되고,
- 스트리밍 중
- 각 스트리밍 청크에서 나온 스타일은 기존 스타일에 계속 이어 붙는다.
- 클라이언트 하이드레이션 이후
- 이후에는 평소와 같이 styled-components가 동작하며, 동적 스타일을 포함한 추가 스타일을 클라이언트에서 직접 주입한다.
- 스타일 레지스트리를 트리의 최상단 Client Component로 두는 이유:
- 서버 렌더링 시 CSS 규칙을 더 효율적으로 추출할 수 있다.
- 매 요청마다 스타일을 다시 생성하지 않고 재사용하여 불필요한 오버헤드를 줄인다.
- 스타일이 Server Component 페이로드에 포함되는 것을 방지하여 전송량을 줄인다.
6. 설계 시 고려사항 정리
-
어떤 컴포넌트에서 CSS-in-JS를 사용할 것인지
- 문서의 전제는, 소개된 CSS-in-JS 라이브러리들이 Client Component에서 사용 된다는 점이다.
- Server Component에서는 CSS Modules, 전역 CSS, Tailwind CSS 등 파일 기반 스타일링을 우선 고려하고, Client Component 경계에서 CSS-in-JS를 사용하는 패턴이 일반적이다(이 부분은 Next.js 전반 스타일링 가이드를 기반으로 한 일반적인 권장 사항이다).
-
React 18 기능 지원 여부 확인
- 사용하는 CSS-in-JS 라이브러리가 Concurrent Rendering, Streaming, Server Components 와의 호환성을 공식적으로 밝히고 있는지 확인하는 것이 중요하다.
-
SSR 및 Streaming과의 통합
useServerInsertedHTML훅과 스타일 레지스트리를 사용하지 않으면, 서버 렌더링 시 스타일이 제대로 삽입되지 않아 FOUC나 스타일 미적용 문제가 발생할 수 있다.
-
성능과 DX 균형
- CSS-in-JS는 컴포넌트 단위로 스타일을 캡슐화하고 타입/테마 통합 등에 장점이 있지만, 런타임 비용이 존재한다.
- SSR/Streaming 환경에서는 스타일 추출 전략과 캐싱 전략을 함께 고려해야 한다.