Next.js App Router Styled-components 적용 에러 및 해결법
- 원인: App Router 컴포넌트는 기본이 서버 컴포넌트라서, 클라이언트 런타임에 스타일 태그를 주입하던 styled-components 방식과 충돌해 FOUC(스타일 없는 화면)나 빌드 에러가 발생
- 해결:
next.config.js에compiler.styledComponents: true설정 + 서버에서 스타일을 수집하는 레지스트리 컴포넌트로 루트 레이아웃 감싸기 - 주의: 다크모드처럼 초기 테마 값을 서버가 미리 알아야 하는 스타일일수록 이 문제를 더 크게 체감함
// next.config.js
compiler: { styledComponents: true }
↓ 왜 이런 에러가 나는지 원리 보기
Next.js App Router에서 Styled-components를 도입하면 첫 페이지가 로드될 때 스타일이 전혀 적용되지 않은 날 것의 HTML이 노출되다가 잠시 뒤 스타일이 입혀지는 FOUC(Flash of Unstyled Content) 현상이나 컴파일 에러를 겪게 됩니다. 이 문제가 생기는 원인과, App Router에서 오류 없이 연동하는 방법을 정리했습니다.
왜 App Router에서 Styled-components 에러가 날까
기존의 Styled-components는 클라이언트 사이드 런타임에 스타일 태그를 생성하고 자바스크립트로 이를 DOM에 주입하는 구조입니다. 하지만 Next.js App Router의 컴포넌트들은 기본적으로 서버 컴포넌트(Server Components)입니다. 즉, 자바스크립트가 브라우저에 도달하기도 전에 서버에서 HTML을 미리 해석하고 렌더링을 끝냅니다. 이 과정에서 Styled-components가 동적으로 생성한 스타일 시트가 정적 HTML 파일에 포함되지 않아 스타일이 완전히 깨진 형태로 전달되는 것입니다. 특히 다크모드처럼 초기 테마 값을 서버에서 미리 알아야 하는 스타일에서 이 문제를 더 크게 체감하게 됩니다.
기본 상태로 연동하면 서버에서 HTML만 먼저 출력되어 순간적으로 스타일 없는 화면이 노출됐다가 JS가 로드된 뒤에야 스타일이 뒤늦게 적용되는 FOUC 현상이 나타납니다. 스타일 레지스트리를 구성해 서버 렌더링 단계에서 스타일 시트를 미리 수집한 뒤 완성된 HTML 헤더에 내장해 주면, 첫 화면부터 스타일이 정상적으로 출력됩니다.
App Router에서 Styled-components 에러를 해결하는 3단계 세팅
1단계: 컴파일러 설정 추가 (next.config.js)
Next.js 내장 SWC 컴파일러에게 Styled-components 활성화 옵션을 명시해 주어야 빌드 단계에서 오류가 나지 않습니다.
/** @type {import('next').NextConfig} */
const nextConfig = {
compiler: {
// SSR 시 컴포넌트의 클래스명이 유실되어 스타일이 깨지는 것을 원천 방지
styledComponents: true,
},
};
module.exports = nextConfig;
2단계: 스타일 레지스트리 컴포넌트 작성
서버에서 생성되는 스타일 정보들을 캡처하여 <head> 영역에 밀어 넣어주는 전용 라이브러리 레지스트리를 생성합니다.
[레지스트리 파일: 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 [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>
);
}
3단계: 루트 레이아웃(layout.tsx) 감싸기
방금 만든 레지스트리 컴포넌트로 어플리케이션의 최상단 HTML 본문(children)을 감싸 스타일 수집을 활성화합니다.
[루트 레이아웃 파일: app/layout.tsx]
import StyledComponentsRegistry from '@/lib/registry';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="ko">
<body>
{/* 생성한 레지스트리로 children을 안전하게 래핑 */}
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
</body>
</html>
);
}
Pages Router에서 App Router로 마이그레이션하던 프로젝트에서, styled-components 세팅을 빼먹은 채로 배포했다가 첫 화면에 스타일이 하나도 안 입혀진 상태로 잠깐 노출된 적이 있습니다. 로컬 개발 서버에서는 이상하게 재현이 잘 안 돼서 원인을 한참 못 찾다가, 프로덕션 빌드로 직접 돌려보고 나서야 서버 렌더링 시점에 스타일 시트가 아예 수집되지 않고 있었다는 걸 알아챘습니다.
요약: CSS-in-JS 연동 문제 해결 체크리스트
CSS-in-JS 라이브러리를 App Router에 연동할 때 점검할 세 가지입니다.
| 점검 영역 | 에러 현상 및 리스크 | 올바른 대안 |
|---|---|---|
| next.config.js | 빌드 시 클래스명 매치 안되어 렌더링 스타일 일그러짐 | styledComponents 활성화 |
| ServerStyleSheet | 서버 렌더링 시 스타일 수집 누락되어 FOUC(화면 깨짐) 발생 | 스타일 레지스트리 구축 |
| RootLayout | 전체 어플리케이션에 캡처 스타일 태그 미적용 | 레지스트리로 최상단 래핑 |
CSS-in-JS의 App Router 연동 대안에 대한 자세한 내용은 공식 문서에서 확인할 수 있습니다.
서버에서 스타일을 미리 수집하는 것도 결국 브라우저가 CSS를 어떤 순서로 읽고 적용하는지 알아야 이해가 됩니다. 기초가 헷갈리신다면 [호마다의 IT 개발 입문 블로그] 의 CSS 기초 글도 참고해보세요.
댓글
댓글 쓰기