웹개발/Next.js

Next.js App Router에서 MUI 쓰면 생기는 'Hydration failed' 에러 해결방법 (with Emotion 설정)

튼튼발자 2025. 4. 10. 10:37
728x90

먼저 에러가 발생한 문제는 다음과 같습니다.

 

  • App Router 기반에서 MUI 사용 시 발생하는 Hydration failed 에러
  • 발생 시점: AppBar, Typography, Button 등 MUI 컴포넌트 사용 시
  • 에러 메시지: "Hydration failed because the server rendered HTML didn't match the client..."

에러

 

 

왜 문제가 발생했냐면,

 

  • Next.js App Router는 SSR을 기본으로 하기 때문에 서버/클라이언트 렌더가 다르면 mismatch 발생했습니다.
  • MUI는 @emotion/react를 사용해 동적 스타일 삽입하고있습니다.
  • 서버에서 렌더된 CSS와 클라이언트에서 렌더된 CSS가 일치하지 않으므로 → 오류가 발생한 상황입니다.

 

MUI 공식 문서에 나와있는 설정방식은 다음과 같습니다. emotion cache를 구성해야하는데요.

Next.js integration - Material UI

 

Next.js integration - Material UI

Learn how to use Material UI with Next.js.

mui.com

// src/app/emotion.tsx
'use client';

import * as React from 'react';
import { CacheProvider } from '@emotion/react';
import createCache from '@emotion/cache';

const emotionCache = createCache({ key: 'css', prepend: true });

export default function EmotionRegistry({ children }: { children: React.ReactNode }) {
  return <CacheProvider value={emotionCache}>{children}</CacheProvider>;
}

 

Next.js App Router 환경에서는 클라이언트에서 이 캐시를 명시적으로 감싸주지 않으면,
SSR 시점과 CSR 시점의 스타일이 일치하지 않아서 hydration 오류가 발생하게 됩니다.
그래서 이 컴포넌트를 만들고 CacheProvider로 전체 앱을 감싸주는 게 필수입니다.

// layout.tsx
import EmotionRegistry from './emotion';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
            <body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>
                <EmotionRegistry> {/* ✅ 여기서 감싸주는 게 핵심 */}
                    <div className="w-[360px] mx-auto min-h-screen bg-white">
                        {children}
                    </div>
                </EmotionRegistry>
            </body>
        </html>
  );
}

여기서는 만든 EmotionRegistry를 <body> 내부에서 감싸주고 있어요.
이렇게 하면 MUI의 모든 컴포넌트가 사용하는 Emotion 스타일이 서버와 클라이언트 모두에서 일관되게 적용됩니다.
결과적으로 Hydration failed... 같은 에러 없이 안정적인 SSR/CSR 환경을 만들 수 있습니다.

아 'use client'와는 별도로 추가로 설정해야하는 겁니다.

728x90