이 글은 Prop `props이름` did not match. 로 시작하는 Warning에 관한 내용입니다.
또한 Next.js에서 styled-components를 사용하면서 겪은 여러 가지 문제를 다룹니다.
_document.tsx(jsx)
Next.js에서 styled-components를 사용할 때 _document를 따로 설정해서 SSR될 때 CSS가 head에 주입되도록 해야 한다. 만약 따로 설정하지 않는다면, styled-components가 적용되지 않은 상태로 렌더링될 수 있다.
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;
babel-plugin-styled-components
Next.js에서 styled-components를 사용하면 위와 같은 경고가 뜨곤 한다.
경고 문구에서도 알 수 있듯이, 서버와 클라이언트의 클래스명이 다른 것이 원인이다.
Next.js는 첫 페이지 로드가 SSR로 동작하기 때문에, 서버에서 생성된 컴포넌트와 CSR로 클라이언트에서 생성된 컴포넌트의 클래스명이 서로 달라지게 된다.
이렇게 환경에 따라 달라지는 className을 일관되게 해주는 것이 바로 babel-plugin-styled-components이다.
$ yarn add -D babel-plugin-styled-components
.babelrc 파일에 아래와 같이 작성하면 된다.
{
"plugins": ["babel-plugin-styled-components"]
}
만약 CNA(create-next-app)을 사용하고 있다면, 루트 디렉토리에 .babelrc 파일을 만들고 아래와 같이 작성하면 된다.
{
"presets": ["next/babel"],
"plugins": ["babel-plugin-styled-components"]
}
아래와 같이 babel-plugin-styled-components에 추가적인 설정을 할 수 있다.
{
"presets": ["next/babel"],
"plugins": [
[
"babel-plugin-styled-components",
{
"ssr": true, // SSR을 위한 설정
"displayName": true, // 클래스명에 컴포넌트 이름을 붙임
"pure": true // dead code elimination (사용되지 않는 속성 제거)
}
]
]
}
+ SWC를 사용하는 경우 (2022/11/13 추가)
Next.js 12버전부터 babel 대신 swc를 사용하여 컴파일하도록 변경되었다. 그러나 .babelrc가 있다면 babel을 사용하게 되므로 위 babel-plugin-styled-components를 사용하지 않고 아예 babelrc를 제거한 다음, next.config.js에서 nextConfig에 styledComponents 설정만 해주면 swc를 사용하면서 동일하게 문제를 해결할 수 있다.
/** @type {import('next').NextConfig} */
const nextConfig = {
compiler: {
styledComponents: true,
},
};
module.exports = nextConfig;
여기까지가 이 오류와 관련하여 검색을 하면 대부분 나오는 내용들이다.
하지만 이렇게 해도 해결이 되지 않을 수 있다.
react-responsive의 useMediaQuery를 사용할 때
function Component() {
const isMobile = useMediaQuery({ query: '(max-width: 767px)'});
return <Tag isMobile={isMobile} />;
}
interface tagProps {
isMobile: boolean;
}
const Tag = styled.div<tagProps>`
max-width: ${({ isMobile }) => isMobile && '200px'};
`;
위와 같이 react-responsive의 useMediaQuery를 사용해서 뷰포트 크기에 따른 styled-components의 분기 처리를 해야 할 경우에도 동일한 경고문이 뜰 수 있다.
위 상황에서 모바일 뷰포트인 767px 이하로 뷰포트를 만들어놓은 상태에서 페이지를 로드하면 아래와 같이 Prop `className` did not match로 시작하는 경고문이 뜬다.
그러나 자세히 보면 맨 처음에 봤던 오류와는 조금 다른 점이 있다.
맨 처음 오류는 서버와 클라이언트의 클래스명이 아예 달랐다면, 이번에는 클래스명은 같으나, 서버에는 1개의 클래스명만 있고 클라이언트에는 2개의 클래스명이 있다는 점이다.
즉, 서버에서는 별도의 스타일이 추가되지 않았고, 클라이언트에서는 erGBsS라는 클래스명에 해당하는 스타일이 추가된 것이다.
useMediaQuery는 내부적으로 window.matchMedia에 의존하고 있다.
그런데, 서버 환경에서는 window가 존재하지 않으므로 정상적으로 isMobile을 구할 수 없다.
이처럼 window가 없는 환경인 서버에서 useMediaQuery는 항상 false를 반환한다.
useEffect로 마운트되었을 때 useMediaQuery로 isMobile을 구하도록 변경하여 해결할 수 있다.
interface hookProps {
query: string;
}
function useBreakpoint(settings: hookProps) {
const [mounted, setMounted] = useState(false);
const value = useMediaQuery(settings);
useEffect(() => {
setMounted(true);
}, []);
return mounted ? value : false;
}
function Component() {
const isMobile = useBreakpoint({ query: '(max-width: 767px)'});
return <Tag isMobile={isMobile} />;
}
useBreakpoint라는 커스텀 훅을 만들고 useEffect로 마운트 될 때 useMediaQuery의 반환값인 value를 반환하고, 마운트되지 않았을 때는 false를 반환하도록 하였다.
console.log(isMobile)을 해보면 서버에서는 항상 false가 출력되고, 클라이언트에서는 false와 true가 각각 한 번씩 출력될 것이다. 클라이언트에서 첫 번째 출력 역시 항상 false인데, 아마 mount 되기 직전에 서버와 클라이언트가 둘 다 스타일이 적용되지 않은 상태로 같아서 경고가 뜨지 않는 것 같다.
Theme과 Typescript 관련 문제
Theme과 관련하여서도 Prop `className` did not match 경고가 뜰 수 있다.
이 부분은 겪어보지는 않았지만, 아래 링크 뒷부분에 자세하게 설명되어 있으니 참고하면 좋을 것 같다.
https://blog.shift.moe/2021/01/02/prop-classname-did-not-match/
Prop `className` did not match – Shifted
Next.js + styled-components에서 Prop `className` did not match가 발생하는 이유와 해결 방법 작년 가을에 solved.ac 프론트엔드를 Typescript로 리팩터하면서 당황스러운 경험을 했습니다. 사이트 내의 아무 링크
blog.shift.moe
참고자료
https://github.com/vercel/next.js/blob/main/examples/with-styled-components/pages/_document.js
GitHub - vercel/next.js: The React Framework
The React Framework. Contribute to vercel/next.js development by creating an account on GitHub.
github.com
Next.js + Typescript + Styled-components 쉽게 구축하기
평소에 next.js로 프로젝트를 구축하는 편이기에, 보일러플레이트를 만들어놓고 쓰면 좋겠다는 생각이들어서 구축하는 김에 포스팅도 같이 해보았습니다 :)
velog.io
https://blog.shift.moe/2021/01/02/prop-classname-did-not-match/
Prop `className` did not match – Shifted
Next.js + styled-components에서 Prop `className` did not match가 발생하는 이유와 해결 방법 작년 가을에 solved.ac 프론트엔드를 Typescript로 리팩터하면서 당황스러운 경험을 했습니다. 사이트 내의 아무 링크
blog.shift.moe
https://seungjoon-lee.oopy.io/d45a150b-9598-4c27-9fea-6ee658a0ac57
styled-components 세팅
styled-components 를 nextjs 에서 사용하기 위해선 SSR 때문에 추가적인 세팅이 필요함
seungjoon-lee.oopy.io
https://nextjs.org/docs/advanced-features/customizing-babel-config
Advanced Features: Customizing Babel Config | Next.js
Extend the babel preset added by Next.js with your own configs.
nextjs.org
https://styled-components.com/docs/tooling#usage
styled-components: Tooling
Additional Tools for styled-components, babel and TypeScript plugins, testing
styled-components.com
[Next] useEffect로 받은 데이터는 SSR될까? ( getInitialProps 사용 )
React useEffect를 사용했을 경우에 SSR이 될까? ☀️ 답 : useEffect안에서 동작하는 데이터는 SSR되지 않는다. useEffect(() => {}, []) 는 마운트 되었을 때, useEffect는 실행된다. 그렇기 때문에 SSR로 받은 htm
velog.io
https://github.com/chakra-ui/chakra-ui/issues/4319
NextJS & SSR Bug: Warning: Prop className did not match on useMediaQuery · Issue #4319 · chakra-ui/chakra-ui
Description On a NextJS application, when using Chakra's useMediaQuery to determine a value for a Text prop, it prints an error about a non matching className on the server and the client Link ...
github.com
'IT > Next.js' 카테고리의 다른 글
[Next.js] React Query로 SSR 구현하기 (1) | 2022.11.18 |
---|---|
[Next.js] Next.js에서 mongoose 연동해서 API 만들기 (0) | 2022.04.03 |