Next.js가 html을 불러올 때 JavaScript의 실행이 한 발짝씩 늦어서 styled-components로 지정된 스타일이 뒤늦게 로드되는 문제를 해결해보겠습니다.

Intro

blink

블로그를 처음 들어오면 이런 깜빡임이 보이는 문제가 있습니다. 자바스크립트에서 선언되는 styled-components의 스타일이 뒤늦게 로드되는건데요, 오늘은 이 문제를 해결해보려고 합니다.

해결

Next.js의 example을 참고했습니다!

1. styled-componentsBabel 플러그인 설치

npm i -D babel-plugin-styled-components
npm i -D babel-plugin-styled-components

devDependency로 설치합니다. 해당 플러그인은 첫 렌더링시에 styled-components를 이용해 스타일을 적용할 수 있게 해줄 뿐만 아니라, 컴포넌트의 hashed className을 환경간 일관되게 유지해줍니다. (SSR에는 필수적으로 필요)

2. ServerStyleSheet_document.tsx에 추가

기존에 이미 만들어뒀던 _document.tsx에 아래 코드를 추가합니다. _document.tsx<html>태그나 <body>태그에 접근할 수 있도록 해줍니다. 번들러로 처음 만들었을 때는 없는데, 커스텀이 필요할 때 생성합니다. 저는 Google Fonts를 로드하는 부분 때문에 미리 만들어뒀었습니다.

import { ServerStyleSheet } from "styled-components"

MyDocument.getInitialProps = async (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()
  }
}
import { ServerStyleSheet } from "styled-components"

MyDocument.getInitialProps = async (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()
  }
}

MyDocument 클래스(Document를 상속하는 클래스입니다)의 getInitialProps() 함수를 정의합니다. 여기에서 ServerStyleSheet를 생성해 모든 페이지에 대해 스타일을 모으고, <App>에 prop으로 넘겨주는 식입니다.

이제 MyDocument에서 <Head>에 아래 한 줄을 추가하면 됩니다.

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang='kr'>
        <Head>
          {this.props.styles} {/* 이 한 줄만 추가하면 됩니다. */}
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}
export default class MyDocument extends Document {
  render() {
    return (
      <Html lang='kr'>
        <Head>
          {this.props.styles} {/* 이 한 줄만 추가하면 됩니다. */}
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    )
  }
}

이제 깜빡임도 사라지고 더 부드럽게 돌아가는 블로그가 됐습니다 😄