
오늘은 Next.js 프로젝트에서 styled-components를 사용할 때 발생하는 스타일이 풀렸다가 다시 적용되는 깜빡임 현상 해결과정을 작성해보겠습니다.
페이지를 새로고침하거나 처음 방문할 때 스타일이 잠깐 사라졌다가 다시 나타나는 현상이 발생. 특히 버튼, 카드, 입력 필드 등의 스타일이 적용되지 않은 상태로 잠깐 보였다가 styled-components가 로드된 후에 스타일이 적용되는 문제였습니다.
Next.js는 다음과 같은 순서로 페이지를 렌더링합니다:
깜빡임 현상의 근본 원인을 이해하기 위해 브라우저의 렌더링 과정을 살펴보겠습니다.
브라우저는 다음과 같은 순서로 페이지를 렌더링합니다:

1단계: HTML 파싱
2단계: CSS 파싱
3단계: DOM, CSSOM 트리 생성
4단계: 렌더 트리 생성
display: none 요소는 제외)5단계: 레이아웃 (Reflow)
6단계: 페인팅
서버사이드 렌더링 (SSR) 시 스타일 누락
하이드레이션 과정에서의 스타일 불일치
먼저 next.config.js에서 styled-components 컴파일러 설정을 확인했습니다:
/** @type {import('next').NextConfig} */
const nextConfig = {
compiler: {
styledComponents: true, // 이 설정이 중요!
},
// ... 기타 설정
};
module.exports = nextConfig;
next.config.js의 역할:
- Next.js의 빌드 설정을 관리하는 파일
compiler.styledComponents: true는 Next.js에게 styled-components를 최적화하도록 지시- 이 설정이 없으면 styled-components가 SSR에서 제대로 작동하지 않음
가장 중요한 해결책은 src/pages/_document.js 파일을 생성하는 것이었습니다:
/* eslint-disable no-unused-vars */
import Document, { Html, Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet } from 'styled-components'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) => {
return sheet.collectStyles(<App {...props} />)
},
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: [
<>
{initialProps.styles}
{sheet.getStyleElement()} // 여기가 핵심!
</>,
],
}
} finally {
sheet.seal()
}
}
render() {
return (
<Html>
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
_document.js의 역할:
- Next.js에서 HTML 문서의 기본 구조를 정의하는 특별한 파일
- 서버사이드 렌더링 시에만 실행됨
<html>,<head>,<body>태그를 커스터마이징 가능- 모든 페이지에서 공통으로 사용되는 HTML 구조를 정의
ServerStyleSheet의 동작 원리:
1.sheet.collectStyles(): 서버에서 렌더링할 때 styled-components 스타일을 수집
2.sheet.getStyleElement(): 수집된 스타일을 HTML<head>에 주입할 수 있는 요소로 변환
3.sheet.seal(): 스타일 수집을 완료하고 더 이상 수정할 수 없도록 봉인
<head>에 스타일이나 메타데이터 주입