들어가며

위 프로젝트는 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)