(Next.js) SSR & Styled-Components Error

Mirrer·2022년 10월 8일
0

Error Handling

목록 보기
4/7
post-thumbnail

Problem Definition

Next.js에서 Styled-Components를 사용함으로서 발생하는 에러와 해결

Next.js에서 Styled-Components를 사용하면 다음과 같은 문제점이 발생한다.


Error 1. Prop 'className' did not match Error

해당 에러는 서버로부터 전달받은 Hash + ClassName이 새로고침 후 클라이언트에서 받은 Hash + ClassName과 일치하지 않아 발생하는 에러이다.

그래서 첫 화면을 SSRRendering하면 오류가 발생하지 않지만, 이후 CSRRendering을 하게되면 발생한다.


Error 2. 페이지 새로고침시 Styled-Components 미적용 Error

Next.js는 먼저 정적으로 생성된 HTMLRendering나머지 JavaSript파일을 로드한다.

이 때 Styled-ComponentsJavaSript에 의해 동적으로 CSS가 생성되는 CSS-In-Js 방식을 사용하기 때문에 위의 과정에서 HTML에 포함되지 않는다.

그래서 Styled-Components가 적용된 페이지를 새로고침하면 style이 풀리게된다.


Resolution

Next.js에서 발생하는 위의 문제들은 다음과 같은 추가적인 세팅으로 해결할 수 있다.


Solution. Error 1

Prop 'className' did not match Error를 해결하기 위해서는 다음과 같은 Babel설정이 필요하다.

Next.js내부적으로 Webpack, Babel이 동작하며, 필요시 다음과 같이 설정을 커스터마이징할 수 있다.

1. Babel Plugin 설치

다음 npm명령어를 통해 Babel Plugin을 설치한다.

npm i babel-plugin-styled-components

2. Babel 커스터마이징

.babelrc파일을 생성하여 다음과 같이 설정한다.

// .babelrc
{
  "presets": ["next/babel"],
  "plugins": [
    ["babel-plugin-styled-components", {
      "ssr": true,
      "displayName": true
    }]
  ]
}

.babelrc파일에서 사용되는 설정의 종류는 다음과 같다.

fileName: 코드가 포함된 파일명
displayName: 클래스명에 해당 스타일 정보 추가
pure: 사용하지 않은 속성 제거
ssr: server side rendering


Solution. Error 2

새로고침시 Styled-Components가 적용되지 않는 에러는 renderPage 함수를 커스터마이징하여 해결한다.

renderPage함수의 커스터마이징은 반드시 CSS-In-Js방식에 한해서만 수정해야 한다.

1. _document.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>
    );
  }
}
profile
memories Of A front-end web developer

0개의 댓글