
Next.js App Router 환경에서 Emotion을 사용하다 보면 아래와 같은 오류를 종종 보게 됩니다.
Warning: Prop `className` did not match
또는:
이번 글에서는 왜 이런 문제가 발생하는지 간단하게 정리해보겠습니다.
Next.js App Router는 기존 Pages Router와 달리 기본적으로 Server Component 기반 구조입니다.
즉 컴포넌트가:
반면 Emotion은:
같은 특징을 가지고 있습니다.
즉 Emotion은 기본적으로 클라이언트 환경에서 동작하는 CSS-in-JS 방식에 더 가깝습니다.
여기서 App Router와 충돌이 발생할 수 있습니다.
Emotion은 렌더링 시점마다 className을 생성합니다.
예를 들면:
css-1abcde
css-92kda
같은 형태의 className이 만들어집니다.
그런데 App Router 환경에서는:
과정을 거치면서 스타일 생성 순서가 달라질 수 있습니다.
그 결과:
Warning: Prop `className` did not match
같은 hydration 오류가 발생하게 됩니다.
React 18 + App Router는 아래 기능들을 적극적으로 사용합니다.
이 환경에서는 컴포넌트 렌더링 순서가 항상 동일하지 않을 수 있습니다.
하지만 Emotion은:
에 영향을 많이 받습니다.
즉:
렌더링 순서가 바뀌는 환경에서 런타임으로 스타일을 생성하다 보니 문제가 발생하는 것
에 가깝습니다.
Warning: Prop `className` did not match
서버와 클라이언트의 className이 달라지는 문제입니다.
초기 렌더 시 스타일이 적용되지 않았다가 뒤늦게 적용되는 현상입니다.
Emotion의 insertion order가 달라지면서 예상과 다른 스타일이 적용될 수 있습니다.
재현 조건이 일정하지 않은 경우가 많아 디버깅도 쉽지 않습니다.
Emotion은 특히:
영향을 크게 받습니다.
그래서 App Router 환경에서는 설정이 조금만 어긋나도 문제가 쉽게 발생합니다.
특히 아래 설정이 중요합니다.
CacheProvider
useServerInsertedHTML
이 설정 없이 사용하면 스타일 충돌 가능성이 높아집니다.
보통은 Emotion Registry를 직접 구성해서 사용합니다.
예를 들면:
'use client'
import { CacheProvider } from '@emotion/react'
import createCache from '@emotion/cache'
그리고 Next.js의:
useServerInsertedHTML()
를 사용해 서버 렌더링 시 style 태그를 직접 삽입합니다.
핵심은:
서버와 클라이언트의 스타일 생성 순서를 최대한 동일하게 맞추는 것
입니다.
최근 App Router 환경에서는 아래 스타일링 방식 선호도가 높아지고 있습니다.
이유는:
같은 장점 때문입니다.
Emotion 자체가 문제 있는 라이브러리는 아닙니다.
다만 Next.js App Router + React 18 환경에서는:
구조와 런타임 스타일 생성 방식이 충돌하기 쉬운 편입니다.
그래서 최근에는 빌드 타임 기반 스타일링 방식도 많이 사용되는 추세입니다.