Next.js로 토이프로젝트를 진행하던 도중에 아래와 같은 오류가 발생하였습니다.
warning: Prop `className` did not match. Server: "MuiFormControl-root sc-iKTcqh gwSKiw css-1nrlq1o-MuiFormControl-root" Client: "MuiFormControl-root sc-gJhJTp gehcyX css-1nrlq1o-MuiFormControl-root"
[발생 원인]
저는 Next.js로 styled-components와 Material-UI를 사용하고 있었습니다. 그런데 왜 이런 경고가 발생했을까요?
이 경고는 서버에서 렌더링된 클래스 이름과 클라이언트에서 렌더링된 클래스 이름이 일치하지 않아서 발생합니다. 주로 SSR환경에서 styled-components와 Material-UI를 함께 사용할 때 발생하는 문제입니다.
Next.js는 기본적으로 모든 페이지를 미리 렌더링(pre-render)합니다. 생성된 각 HTML은 해당 페이지에 필요한 최소한의 자바스크립트 코드와 연결되며, 브라우저가 페이지를 로드하면 자바스크립트 코드가 완전히 인터랙티브 하게 만들어집니다. 이 과정을 hydration이라고 부릅니다.
그런데, styled-components와 Material-UI는 각각 고유의 클래스 이름 생성 방식을 사용합니다. SSR에서는 서버에서 생선 된 HTML을 클라이언트에서 다시 렌더링 합니다. 서버와 클라이언트가 서로 다른 클래스 이름을 생성하면, 클래스 이름 불일치가 발생해 버립니다.
[해결 방법]
1. _document.ts 파일에서 서버 사이드 렌더링 시 styled-components와 Material-UI 스타일을 올바르게 처리하도록 합니다.
import Document, {
Html,
Head,
Main,
NextScript,
DocumentContext
} from 'next/document';
import { ServerStyleSheet } from 'styled-components';
class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
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>
);
}
}
export default MyDocument;
2. next.config에서 styledComponents를 true로 설정해 줍니다.
/** @type {import('next').NextConfig} */
const nextConfig = {
compiler: {
styledComponents: true
}
};
module.exports = nextConfig;
이러면 콘솔에 경고가 깔끔하게 지워졌을 겁니다. 이 설정을 통해 서버와 클라이언트 모두에서 일관된 스타일을 적용할 수 있으며, 클래스 이름 불일치 오류를 해결할 수 있습니다.
References
https://blog.hwahae.co.kr/all/tech/13604
https://tesseractjh.tistory.com/164