들어가며
위 프로젝트는 Typescript + Next15 + styled-components + MUI 환경을 지원합니다.
다만, 기존에 styled-components가 설치되신 분들은 styled 임포트 시 mui 라이브러리를 임포트하는 것에 유의 부탁드립니다. (실수 가능성이 높습니다.)
템플릿
https://github.com/LDK1009/Next_Start_Template
GitHub - LDK1009/Next_Start_Template: Typescript + Next15 + styled-components + MUI 프로젝트 템플릿입니다.
Typescript + Next15 + styled-components + MUI 프로젝트 템플릿입니다. - LDK1009/Next_Start_Template
github.com
배경

자꾸 이런 오류 떠서 화나서 프로젝트 구축 방법 A to Z를 작성합니다.
왜냐하면 문제 원인이 무지성 개발로 인한 라이브러리 세팅 오류 때문이죠 ㅎㅎ
문제 원인
MUI는 React를 기반으로 개발되었기 때문에 기본적으로 CSR을 우선 지원합니다.
이 과정에서 MUI의 스타일 시스템은 HTML을 렌더링한 후 생성된 CSS를 <head>에 추가하는 방식으로 동작합니다.
하지만 Next.js에서는 SSR을 사용할 경우, 이러한 스타일 생성 과정이 서버와 클라이언트 간의 스타일 불일치를 초래할 수 있습니다.
이를 방지하기 위해 <AppRouterCacheProvider />를 사용하여 스타일 캐싱을 맞춰주는 것이 필요합니다.
여러분은 저와 같은 문제를 겪지 않기를 바라며 글을 시작하겠습니다....
설치
Next.js 프로젝트 생성
npx create-next-app@latest project
cd ./project
MUI 설치
npm install @mui/material @emotion/react @emotion/styled
npm install @mui/material @mui/styled-engine-sc styled-components
npm install @mui/icons-material
npm install @mui/material-nextjs @emotion/cache
해결방법
MUI 스타일 테마 생성
Theming - Material UI
Customize Material UI with your theme. You can change the colors, the typography and much more.
mui.com
src/styles/theme.ts
import { createTheme } from "@mui/material/styles";
declare module "@mui/material/styles" {
interface Palette {
gray: {
0: string;
25: string;
50: string;
75: string;
100: string;
200: string;
300: string;
400: string;
500: string;
600: string;
700: string;
800: string;
900: string;
};
}
interface PaletteOptions {
gray?: {
0: string;
25: string;
50: string;
75: string;
100: string;
200: string;
300: string;
400: string;
500: string;
600: string;
700: string;
800: string;
900: string;
};
}
}
export const muiTheme = createTheme({
palette: {
primary: {
light: "#42A5F5",
main: "#2196F3",
dark: "#1E88E5",
},
secondary: {
main: "#BA68C8",
light: "#9C27B0",
dark: "#7B1FA2",
},
info: {
main: "#01579B",
light: "#0288D1",
dark: "#03A9F4",
},
success: {
main: "#4CAF50",
light: "#2E7D32",
dark: "#1B5E20",
},
warning: {
main: "#FF9800",
light: "#E65100",
dark: "#EF6C00",
},
error: {
main: "#EF5350",
light: "#D32F2F",
dark: "#C62828",
},
gray: {
0: "#FFFFFF",
25: "#F2F2F7",
50: "#E5E5EA",
75: "#D1D1D6",
100: "#C7C7CC",
200: "#AEAEB2",
300: "#8E8E93",
400: "#636366",
500: "#48484A",
600: "#3A3A3C",
700: "#2C2C2E",
800: "#1C1C1E",
900: "#131314",
},
},
});
MUI 테마 프로바이더 생성
src/styles/ThemeProviderWrapper.tsx
"use client";
import { ThemeProvider as MuiThemeProvider } from "@mui/material/styles";
import { muiTheme } from "@/styles/theme";
import { CssBaseline } from "@mui/material";
const ThemeProviderWrapper = ({ children }: { children: React.ReactNode }) => {
return (
<MuiThemeProvider theme={muiTheme}>
<CssBaseline />
{children}
</MuiThemeProvider>
);
};
export default ThemeProviderWrapper;
MUI 테마 프로바이더 적용
src/app/layout.tsx
import ThemeProviderWrapper from "@/styles/wrapper/ThemeProviderWrapper";✨
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "데브카드(DevCard) | 개발자 면접 준비를 위한 면접질문 카드",
description: "데브카드는 개발자 면접 준비를 위한 면접질문 카드를 제공합니다.",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="ko">
<body>
<ThemeProviderWrapper>✨
{children}
</ThemeProviderWrapper>✨
</body>
</html>
);
}
next.config 설정
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
compiler: {
emotion: true, ✨
},
};
export default nextConfig;
MUI <AppRouterCacheProvider /> 적용
npm install @mui/material-nextjs @emotion/cache
import ThemeProviderWrapper from "@/components/wrapper/ThemeProviderWrapper";
import type { Metadata } from "next";
import { AppRouterCacheProvider } from "@mui/material-nextjs/v15-appRouter"; ✨
export const metadata: Metadata = {
title: "데브카드(DevCard) | 개발자 면접 준비를 위한 면접질문 카드",
description: "데브카드는 개발자 면접 준비를 위한 면접질문 카드를 제공합니다.",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="ko">
<body>
<AppRouterCacheProvider> ✨
<ThemeProviderWrapper>
{children}
</ThemeProviderWrapper>
</AppRouterCacheProvider> ✨
</body>
</html>
);
}
주의사항
1. styled 모듈은 @mui/material의 것을 사용해야한다(기존에 styled-components가 설치되어있다면 헷갈림)
2. 스타일드 컴포넌트를 생성할 때 함수형으로 작성해야한다.( styled.div X, styled('div') O)
"use client";
import { styled } from "@mui/material";
const TestBox = () => {
return (
<div>
<RedBox />
</div>
);
};
export default TestBox;
const RedBox = styled("div")`
width: 100px;
height: 100px;
background-color: red;
background-color: ${({ theme }) => theme.palette.primary.main};
`;
참고자료
// 디펜던시
"dependencies": {
"@emotion/cache": "^11.14.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/icons-material": "^6.4.5",
"@mui/material": "^6.4.5",
"@mui/material-nextjs": "^6.4.3",
"@supabase/supabase-js": "^2.49.1",
"axios": "^1.7.9",
"next": "15.1.7",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"styled-components": "^6.1.15",
"swiper": "^11.2.4",
"zustand": "^5.0.3"
},
https://mui.com/material-ui/integrations/nextjs/
Next.js integration - Material UI
Learn how to use Material UI with Next.js.
mui.com
https://nextjs.org/docs/messages/react-hydration-error
Text content does not match server-rendered HTML
Using App Router Features available in /app
nextjs.org
https://sooncoding.tistory.com/245
[MUI] Next.js 15 + styled-components + MUI 전역 스타일 설정 방법 | 테마 설정 방법 |
이 글은 next.js 15 + styled-components + MUI 를 사용한 프로젝트에 대해 다룹니다.이 글의 테마는 MUI의 디자인 시스템 컬러를 참고하였습니다. 0. 코드 템플릿 1. 테마 생성src/styles/theme.tsimport { createTheme }
sooncoding.tistory.com
오류 코드
Error: Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:
- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.
It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.
https://react.dev/link/hydration-mismatch
...
<body>
<StyledComponentsRegistry>
<ThemeProviderWrapper>
<ThemeProvider theme={{...}}>
<ThemeProviderNoVars theme={{...}}>
<ThemeProvider themeId={undefined} theme={{...}}>
<ThemeProvider theme={{...}}>
<RtlProvider value={false}>
<DefaultPropsProvider value={{}}>
<CssBaseline>
<Sidebar>
+ <div>
- <style data-emotion="css-global o6gwfi" data-s="">
...
at throwOnHydrationMismatch (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc._.js:2659:56)
at beginWork (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc._.js:5413:918)
at runWithFiberInDEV (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc._.js:631:20)
at performUnitOfWork (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc._.js:7955:97)
at workLoopConcurrent (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc._.js:7951:58)
at renderRootConcurrent (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc._.js:7933:71)
at performWorkOnRoot (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc._.js:7565:175)
at performWorkOnRootViaSchedulerTask (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc._.js:8394:9)
at MessagePort.performWorkUntilDeadline (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_107ce8._.js:2353:64)
'프론트엔드 > Next.js' 카테고리의 다른 글
[Next.js] Next15 서버 컴포넌트에서 동적 세그먼트 파라미터 가져오기 | 동적 메타데이터 설정하기 | Next15 파라미터 관련 배포 오류 (0) | 2025.03.05 |
---|---|
[Next.js] Next15 API Routes 를 이용해 OgData 추출하고 링크 북마크 만들기 (0) | 2025.03.04 |
[Next.js] Next 15 l SEO 완벽 가이드 A to Z (0) | 2025.02.13 |
[Next.js] Next15 버전 SEO 설정 (0) | 2025.02.13 |
[Next.js] Next.js 15에서 <script/> 태그 삽입 오류 해결 (0) | 2025.01.10 |
들어가며
위 프로젝트는 Typescript + Next15 + styled-components + MUI 환경을 지원합니다.
다만, 기존에 styled-components가 설치되신 분들은 styled 임포트 시 mui 라이브러리를 임포트하는 것에 유의 부탁드립니다. (실수 가능성이 높습니다.)
템플릿
https://github.com/LDK1009/Next_Start_Template
GitHub - LDK1009/Next_Start_Template: Typescript + Next15 + styled-components + MUI 프로젝트 템플릿입니다.
Typescript + Next15 + styled-components + MUI 프로젝트 템플릿입니다. - LDK1009/Next_Start_Template
github.com
배경

자꾸 이런 오류 떠서 화나서 프로젝트 구축 방법 A to Z를 작성합니다.
왜냐하면 문제 원인이 무지성 개발로 인한 라이브러리 세팅 오류 때문이죠 ㅎㅎ
문제 원인
MUI는 React를 기반으로 개발되었기 때문에 기본적으로 CSR을 우선 지원합니다.
이 과정에서 MUI의 스타일 시스템은 HTML을 렌더링한 후 생성된 CSS를 <head>에 추가하는 방식으로 동작합니다.
하지만 Next.js에서는 SSR을 사용할 경우, 이러한 스타일 생성 과정이 서버와 클라이언트 간의 스타일 불일치를 초래할 수 있습니다.
이를 방지하기 위해 <AppRouterCacheProvider />를 사용하여 스타일 캐싱을 맞춰주는 것이 필요합니다.
여러분은 저와 같은 문제를 겪지 않기를 바라며 글을 시작하겠습니다....
설치
Next.js 프로젝트 생성
npx create-next-app@latest project
cd ./project
MUI 설치
npm install @mui/material @emotion/react @emotion/styled
npm install @mui/material @mui/styled-engine-sc styled-components
npm install @mui/icons-material
npm install @mui/material-nextjs @emotion/cache
해결방법
MUI 스타일 테마 생성
Theming - Material UI
Customize Material UI with your theme. You can change the colors, the typography and much more.
mui.com
src/styles/theme.ts
import { createTheme } from "@mui/material/styles";
declare module "@mui/material/styles" {
interface Palette {
gray: {
0: string;
25: string;
50: string;
75: string;
100: string;
200: string;
300: string;
400: string;
500: string;
600: string;
700: string;
800: string;
900: string;
};
}
interface PaletteOptions {
gray?: {
0: string;
25: string;
50: string;
75: string;
100: string;
200: string;
300: string;
400: string;
500: string;
600: string;
700: string;
800: string;
900: string;
};
}
}
export const muiTheme = createTheme({
palette: {
primary: {
light: "#42A5F5",
main: "#2196F3",
dark: "#1E88E5",
},
secondary: {
main: "#BA68C8",
light: "#9C27B0",
dark: "#7B1FA2",
},
info: {
main: "#01579B",
light: "#0288D1",
dark: "#03A9F4",
},
success: {
main: "#4CAF50",
light: "#2E7D32",
dark: "#1B5E20",
},
warning: {
main: "#FF9800",
light: "#E65100",
dark: "#EF6C00",
},
error: {
main: "#EF5350",
light: "#D32F2F",
dark: "#C62828",
},
gray: {
0: "#FFFFFF",
25: "#F2F2F7",
50: "#E5E5EA",
75: "#D1D1D6",
100: "#C7C7CC",
200: "#AEAEB2",
300: "#8E8E93",
400: "#636366",
500: "#48484A",
600: "#3A3A3C",
700: "#2C2C2E",
800: "#1C1C1E",
900: "#131314",
},
},
});
MUI 테마 프로바이더 생성
src/styles/ThemeProviderWrapper.tsx
"use client";
import { ThemeProvider as MuiThemeProvider } from "@mui/material/styles";
import { muiTheme } from "@/styles/theme";
import { CssBaseline } from "@mui/material";
const ThemeProviderWrapper = ({ children }: { children: React.ReactNode }) => {
return (
<MuiThemeProvider theme={muiTheme}>
<CssBaseline />
{children}
</MuiThemeProvider>
);
};
export default ThemeProviderWrapper;
MUI 테마 프로바이더 적용
src/app/layout.tsx
import ThemeProviderWrapper from "@/styles/wrapper/ThemeProviderWrapper";✨
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "데브카드(DevCard) | 개발자 면접 준비를 위한 면접질문 카드",
description: "데브카드는 개발자 면접 준비를 위한 면접질문 카드를 제공합니다.",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="ko">
<body>
<ThemeProviderWrapper>✨
{children}
</ThemeProviderWrapper>✨
</body>
</html>
);
}
next.config 설정
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
/* config options here */
compiler: {
emotion: true, ✨
},
};
export default nextConfig;
MUI <AppRouterCacheProvider /> 적용
npm install @mui/material-nextjs @emotion/cache
import ThemeProviderWrapper from "@/components/wrapper/ThemeProviderWrapper";
import type { Metadata } from "next";
import { AppRouterCacheProvider } from "@mui/material-nextjs/v15-appRouter"; ✨
export const metadata: Metadata = {
title: "데브카드(DevCard) | 개발자 면접 준비를 위한 면접질문 카드",
description: "데브카드는 개발자 면접 준비를 위한 면접질문 카드를 제공합니다.",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="ko">
<body>
<AppRouterCacheProvider> ✨
<ThemeProviderWrapper>
{children}
</ThemeProviderWrapper>
</AppRouterCacheProvider> ✨
</body>
</html>
);
}
주의사항
1. styled 모듈은 @mui/material의 것을 사용해야한다(기존에 styled-components가 설치되어있다면 헷갈림)
2. 스타일드 컴포넌트를 생성할 때 함수형으로 작성해야한다.( styled.div X, styled('div') O)
"use client";
import { styled } from "@mui/material";
const TestBox = () => {
return (
<div>
<RedBox />
</div>
);
};
export default TestBox;
const RedBox = styled("div")`
width: 100px;
height: 100px;
background-color: red;
background-color: ${({ theme }) => theme.palette.primary.main};
`;
참고자료
// 디펜던시
"dependencies": {
"@emotion/cache": "^11.14.0",
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@mui/icons-material": "^6.4.5",
"@mui/material": "^6.4.5",
"@mui/material-nextjs": "^6.4.3",
"@supabase/supabase-js": "^2.49.1",
"axios": "^1.7.9",
"next": "15.1.7",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"styled-components": "^6.1.15",
"swiper": "^11.2.4",
"zustand": "^5.0.3"
},
https://mui.com/material-ui/integrations/nextjs/
Next.js integration - Material UI
Learn how to use Material UI with Next.js.
mui.com
https://nextjs.org/docs/messages/react-hydration-error
Text content does not match server-rendered HTML
Using App Router Features available in /app
nextjs.org
https://sooncoding.tistory.com/245
[MUI] Next.js 15 + styled-components + MUI 전역 스타일 설정 방법 | 테마 설정 방법 |
이 글은 next.js 15 + styled-components + MUI 를 사용한 프로젝트에 대해 다룹니다.이 글의 테마는 MUI의 디자인 시스템 컬러를 참고하였습니다. 0. 코드 템플릿 1. 테마 생성src/styles/theme.tsimport { createTheme }
sooncoding.tistory.com
오류 코드
Error: Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used:
- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.
It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.
https://react.dev/link/hydration-mismatch
...
<body>
<StyledComponentsRegistry>
<ThemeProviderWrapper>
<ThemeProvider theme={{...}}>
<ThemeProviderNoVars theme={{...}}>
<ThemeProvider themeId={undefined} theme={{...}}>
<ThemeProvider theme={{...}}>
<RtlProvider value={false}>
<DefaultPropsProvider value={{}}>
<CssBaseline>
<Sidebar>
+ <div>
- <style data-emotion="css-global o6gwfi" data-s="">
...
at throwOnHydrationMismatch (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc._.js:2659:56)
at beginWork (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc._.js:5413:918)
at runWithFiberInDEV (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc._.js:631:20)
at performUnitOfWork (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc._.js:7955:97)
at workLoopConcurrent (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc._.js:7951:58)
at renderRootConcurrent (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc._.js:7933:71)
at performWorkOnRoot (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc._.js:7565:175)
at performWorkOnRootViaSchedulerTask (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_react-dom_1f56dc._.js:8394:9)
at MessagePort.performWorkUntilDeadline (http://localhost:3000/_next/static/chunks/node_modules_next_dist_compiled_107ce8._.js:2353:64)
'프론트엔드 > Next.js' 카테고리의 다른 글
[Next.js] Next15 서버 컴포넌트에서 동적 세그먼트 파라미터 가져오기 | 동적 메타데이터 설정하기 | Next15 파라미터 관련 배포 오류 (0) | 2025.03.05 |
---|---|
[Next.js] Next15 API Routes 를 이용해 OgData 추출하고 링크 북마크 만들기 (0) | 2025.03.04 |
[Next.js] Next 15 l SEO 완벽 가이드 A to Z (0) | 2025.02.13 |
[Next.js] Next15 버전 SEO 설정 (0) | 2025.02.13 |
[Next.js] Next.js 15에서 <script/> 태그 삽입 오류 해결 (0) | 2025.01.10 |