[Next.js] CSS in JS 사용시 HTML렌더링 시 스타일이 적용되지 않는 문제

sujin·2022년 11월 21일
4

Next.js

목록 보기
2/5
post-thumbnail

Next.js 를 알게 된지 얼마 안됐을 때, 예상보다 빨리 문제가 생겼는데...🥲
그것은 바로 styled-components로 작업을 하던 중 처음 브라우저가 로딩 되었을 때는 스타일이 적용됐지만 새로고침을 하면 스타일이 없어지는 현상이었다.

새로고침 전(스타일이 적용된 모습)

새로고침 후(스타일이 적용되지 않은 모습)

결론적으로 이런 문제가 발생한 이유는 Next.js의 Pre-Rendering의 특징 때문이었는데 이것에 대해 좀 더 자세히 알아보고 해결 방법에 대해서 정리 해 보도록 하자!!



Pre-Rendering 란?

Pre-RenderingNext.js에서 가장 중요한 개념이라고 할 수 있다.
기본적으로 Next.js는 모든 페이지를 pre-rendering 한다.

이 말이 무슨 말이냐 하면, pre-rendering 이란 각 페이지에 대한 HTML을 미리 생성한다는 것을 의미한다.

Pre-Rendering가 필요한 이유

Pre-Rendering이 각 페이지에 대한 HTML을 미리 생성한다는 것을 알았다면 왜 HTML을 미리 만들어 주는 것인지에 대한 의문이 생길 수 있다. 그렇다면 Pre-Rendering의 유무 차이에 대해서 한번 살펴보자.

Pre-Rendering 사용하지 않을 때 (React.js 만 사용)

  • JS 전체가 로드되어야 하기 때문에 최초 Load가 되었을 때는 사용자에게 화면이 보여지지 않게 된다.
  • 만약 전체 페이지가 로드되기 전까지 빈 화면을 봐야한다면 사용자가 불편함을 느낄 수 있다.

Pre-Rendering 사용했을 때 (Next.js 사용)

  • 최초 Load가 되었을 때 JS동작만 없는 HTML을 먼저 화면에 보여준다.
  • 만약 사용자에 따라 JS를 꺼놓거나, 브라우저 버전이 낮아서 리액트를 실행시킬 수 없는 상황이라해도 Next.js에서 미리 만들어둔 HTML화면을 볼 수 있게 된다.
  • 하지만, 아직 JS파일이 로드되기 전이기 때문에 Link와 같은 컴포넌트는 동작하지 않는다.

정리하자면, SSR로 생성된 HTML은 해당 페이지에 필요한 최소한의 JS코드로 되어있으며, JS파일을 서버로부터 모두 받아오게 되면 페이지를 완벽히 인터렉티브하게 만들게 되며, 이 과정을 hydration 이라고 한다

hydration

hydration 이란 위에서도 말했듯이, HTML이 가지고 있던 JS가 실행되면서 완전한 페이지가 되는 과정 이라고 생각하면 된다.

좀 더 자세하게 풀어서 이야기 하자면,

  • 최초 Load가 되면 HTML가 생성된다.
  • 각 생성된 HTML들은 필요한 최소한의 JS코드만 있다.
  • 페이지가 브라우저에 의해 로드되면 JS코드가 실행되고, 그 코드가 완전히 동작가능한 페이지로 만들어준다.

이 과정을 hydration이라고 한다.



그래서 문제가 생긴 이유는?

서론이 길었는데, 나에게 발생 했던 문제는 styled-component로 작업한 스타일이 새로고침을 하면 사라지는 문제였다.

이런 문제가 발생한 이유는 Next.jsPre-Rendering을 하면서 HTML을 로드하고 hydration하는과정에서 다른 파일들을 로드하기 때문에 발생했다.

우리가 SSG를 이용해 static한 HTML파일을 생성한 뒤 SSR환경을 구축했다 하더라도, JS에 의해 동적으로 CSS가 생성되는 CSS-in-JS 방식인 styled-component는 SSG과정에서 생성되는 HTML에 코드가 함께 build 되지 않는다.

여기서 잠깐✋🏻

CSR vs SSR vs SSG

  • CSR(Client Side Rendring) : 화면을 클라이언트단에서 바꾸는 기법
  • SSR(Server Side Rendring) : 화면을 서버단에서 전송해주는 기법
  • SSG(Server Side Generation) : 화면을 서버에서 미리 만들어 전송해주는 기법
    👀자세한 설명


그렇다면 해결 방법은?

Next.js 공식 문서 에 나와있는 renderPage 함수로 해결 할 수 있었다.

1. _document.js 파일 생성

pages 디렉토리에 _document.js 파일을 생성한다.

import Document, { Html, Head, Main, NextScript } from 'next/document'

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const originalRenderPage = ctx.renderPage
    ctx.renderPage = () =>
      originalRenderPage({
        enhanceApp: (App) => App,
        enhanceComponent: (Component) => Component,
      })
    
    const initialProps = await Document.getInitialProps(ctx)

    return initialProps
  }

  render() {
    return (
      <Html>
        <Head />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

export default MyDocument

2. ServerStyleSheet 함수를 import

import Document, { Html, Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet } from 'styled-components';

class MyDocument extends Document {
  ... 생략

3. renderPage 함수 조건 추가

import Document, { Html, Head, Main, NextScript } from 'next/document'
import { ServerStyleSheet } from 'styled-components';

class MyDocument extends Document {
  static async getInitialProps(ctx) {
    const originalRenderPage = ctx.renderPage
    const sheet = new ServerStyleSheet();
    
    ctx.renderPage = () =>
      originalRenderPage({
        enhanceApp: (App) => App,
        enhanceComponent: (Component) => Component,
      })
    
    const initialProps = await Document.getInitialProps(ctx)

    return {
    	...initialProps,
      	styles: [
          <>
            {initialProps.styles}
            {sheet.getStyleElement()}
          </>,
        ],
    }
  }

 ...생략

위의 해결 방법을 정리하자면,

  • pages 디렉토리에 HTML custom을 설정할 수 있는 _document.js 파일을 생성한다.
  • ServerStyleSheet 함수를 styled-components에서 impot하여 Global하게 설정한다.
  • renderPage 함수로 렌더링 조건을 Customizing 한다.

마무리

해결방안을 찾다가 pre-rendering에 대해서 더 자세히 알아보고 정리도 해봤는데,
next.js의 중요한 개념이라는 것을 공부할 수 록 깨닫게 되어서 나중에 따로 이 부분에 대해서 더 공부를 해봐야겠다.

profile
개발댕발

2개의 댓글

comment-user-thumbnail
2022년 11월 21일

오... HoneyTip 감사합니다...

1개의 답글