Next.js
에서Styled-Components
를 사용함으로서 발생하는 에러와 해결
Next.js
에서 Styled-Components
를 사용하면 다음과 같은 문제점이 발생한다.
해당 에러는 서버로부터 전달받은 Hash
+ ClassName
이 새로고침 후 클라이언트에서 받은 Hash
+ ClassName
과 일치하지 않아 발생하는 에러이다.
그래서 첫 화면을 SSR
로 Rendering
하면 오류가 발생하지 않지만, 이후 CSR
로 Rendering
을 하게되면 발생한다.
Next.js
는 먼저 정적으로 생성된 HTML
을 Rendering
후 나머지 JavaSript
파일을 로드한다.
이 때 Styled-Components
는 JavaSript
에 의해 동적으로 CSS
가 생성되는 CSS-In-Js
방식을 사용하기 때문에 위의 과정에서 HTML
에 포함되지 않는다.
그래서 Styled-Components
가 적용된 페이지를 새로고침하면 style이 풀리게된다.
Next.js
에서 발생하는 위의 문제들은 다음과 같은 추가적인 세팅으로 해결할 수 있다.
Prop 'className' did not match Error
를 해결하기 위해서는 다음과 같은 Babel설정이 필요하다.
Next.js
도 내부적으로 Webpack
, Babel
이 동작하며, 필요시 다음과 같이 설정을 커스터마이징할 수 있다.
다음 npm명령어를 통해 Babel Plugin
을 설치한다.
npm i babel-plugin-styled-components
.babelrc
파일을 생성하여 다음과 같이 설정한다.
// .babelrc
{
"presets": ["next/babel"],
"plugins": [
["babel-plugin-styled-components", {
"ssr": true,
"displayName": true
}]
]
}
.babelrc
파일에서 사용되는 설정의 종류는 다음과 같다.
fileName
: 코드가 포함된 파일명
displayName
: 클래스명에 해당 스타일 정보 추가
pure
: 사용하지 않은 속성 제거
ssr
: server side rendering
새로고침시 Styled-Components
가 적용되지 않는 에러는 renderPage
함수를 커스터마이징하여 해결한다.
renderPage
함수의 커스터마이징은 반드시 CSS-In-Js
방식에 한해서만 수정해야 한다.
pages
폴더에 _document.js
파일 생성하여 다음과 같이 셋팅한다.
_document.js
파일은 _app.js
파일 보다 위에서 동작하며, 각 페이지가 초기화될 때 HTML
페이지 중 Document
부분에 대한 오버 라이딩을 제공한다.
특히, <html>
과 <body>
태그에 대한 오버라이딩을 제공하기 때문에 사용자가 원하는 방식으로 페이지를 제어할 수 있다.
// pages/_document.js
import React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import { ServerStyleSheet } from 'styled-components';
export default class MyDocument extends Document {
static async getInitialProps(ctx) { // _app.js, _document.js에서만 사용되는 특수한 SSR 메서드
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () => originalRenderPage({
// 기존 document기능에 styled-components를 SSR
enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />),
})
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
};
} catch (error) {
console.error(error)
} finally {
sheet.seal();
}
}
render() {
return (
<Html>
<Head />
<body>
{/* polyfill은 기본적으로 지원하지 않는 이전 브라우저에서 최신 기능을 제공하는 데 필요한 코드 (https://polyfill.io/v3/url-builder/) */}
{/* NextScript보다 위에 적용 */}
<script src="https://polyfill.io/v3/polyfill.min.js?features=default%2Ces2015%2Ces2016%2Ces2017%2Ces2018%2Ces2019"/>
<Main />
<NextScript />
</body>
</Html>
);
}
}