λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
πŸ‘¨‍πŸ’» web.dev/fe

Next.js styled-component μ„€μ •ν•˜κΈ° (SSR FOUC 이슈)

by HandHand 2022. 11. 27.

 

πŸ“Œ Next.js ❀️ styled-component

Next.js 와 Styled Component λ₯Ό ν•¨κ»˜ μ‚¬μš©ν•  λ•Œ μ μ ˆν•œ 섀정을 ν•˜μ§€ μ•ŠμœΌλ©΄

SSR μ‹œμ— FOUC (Flash of Unstyled Content) ν˜„μƒμ΄ λ°œμƒν•˜κ²Œ λ©λ‹ˆλ‹€.

 

πŸ€” FOUC?

νŽ˜μ΄μ§€μ˜ μŠ€νƒ€μΌμ‹œνŠΈ 정보가 λ‘œλ“œ μ‹œμ μ— ν¬ν•¨λ˜μ§€ μ•Šμ•„

μž μ‹œ μŠ€νƒ€μΌμ΄ μ μš©λ˜μ§€ μ•Šμ€ νŽ˜μ΄μ§€κ°€ λ‚˜νƒ€λ‚˜λŠ” ν˜„μƒμž…λ‹ˆλ‹€.

 

πŸ“Œ FOUC 원인 νŒŒμ•…ν•˜κΈ°

Next.js SSR (Pre Rendering)

Server-side Rendering κ³Ό Static Generation λͺ¨λ‘ Pre-rendering 방식이며

ν•„μš”ν•œ data fetching κ³Ό HTML λ Œλ”λ§μ΄ client μ—κ²Œ 보내지기 전에 이미 μˆ˜ν–‰λ©λ‹ˆλ‹€.

이λ₯Ό 톡해 client λŠ” non-interactive ν•œ νŽ˜μ΄μ§€λ₯Ό λΉ λ₯Έ μ‹œκ°„μ— 전달 받을 수 있고,

μ»΄ν¬λ„ŒνŠΈμ— ν•„μš”ν•œ 이벀트 λ¦¬μŠ€λ„ˆμ™€ 같은 interactive 속성듀은 λ³„λ„μ˜ λ²ˆλ“€λ‘œ 전달받아

client λ‹¨μ—μ„œ hydration 이 이루어진 λ’€ μš°λ¦¬κ°€ μƒν˜Έμž‘μš©ν•˜λŠ” 웹앱이 λ©λ‹ˆλ‹€.

 

css-in-js

이 λ•Œλ¬Έμ— ssr μ—μ„œ styled component 와 같은 css-in-js λ₯Ό μ‚¬μš©ν•  λ•Œ λ¬Έμ œκ°€ λ°œμƒν•©λ‹ˆλ‹€.

hydrate κ³Όμ •μ—μ„œ λ‹€μš΄λ‘œλ“œ 받은 JS λ₯Ό 톡해 μ§€μ •λœ μŠ€νƒ€μΌ 정보가 생기기 λ•Œλ¬Έμ—

νŽ˜μ΄μ§€ λ‘œλ“œμ‹œ 잠깐 μŠ€νƒ€μΌμ΄ λˆ„λ½λœ 화면이 λ³΄μ—¬μ§€κ²Œ λ©λ‹ˆλ‹€.

μ΄λŠ” μ‚¬μš©μžμ—κ²Œ 쒋지 μ•Šμ€ UX κ²½ν—˜μ„ μ œκ³΅ν•  수 μžˆλŠ” μš”μ†Œλ‘œ λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

 

πŸ“Œ Styled Component SSR μ„€μ •

λ‹€ν–‰νžˆλ„ styled-component λŠ” SSR λ₯Ό μ§€μ›ν•˜λ©° 이 문제λ₯Ό ν•΄κ²°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Next.js 버전에 따라 swc ν˜Ήμ€ babel μ‚¬μš© μ—¬λΆ€κ°€ λ‹€λ₯΄κΈ° λ•Œλ¬Έμ— λŒ€μ‘ 방법이 λ‹€λ¦…λ‹ˆλ‹€.

 

1️⃣ Next.js v11 μ΄ν•˜

μ•ˆμ •μ μΈ ssr λ₯Ό μœ„ν•΄ babel-plugin-styled-components ν”ŒλŸ¬κ·ΈμΈ 섀정이 ν•„μš”ν•©λ‹ˆλ‹€.

ν•΄λ‹Ή ν”ŒλŸ¬κ·ΈμΈμ„ 톡해 각각의 styled-component 의 checksum 뢈일치λ₯Ό λ°©μ§€ν•©λ‹ˆλ‹€.

 

// .babelrc

{
  "plugins": ["babel-plugin-styled-components"]
}

 

이후 custom Document λ₯Ό μ •μ˜ν•œ λ’€, getInitialProps μ—μ„œ 각각의 νŽ˜μ΄μ§€μ— λ“€μ–΄κ°ˆ <head> νƒœκ·Έμ—

SSR μ—μ„œ λ°˜μ˜λ˜μ–΄μ•Ό ν•  styled-component 정보λ₯Ό μ£Όμž…ν•©λ‹ˆλ‹€.

 

// pages/_document.js

import Document, { DocumentContext, DocumentInitialProps } from 'next/document'
import { ServerStyleSheet } from 'styled-components'

export default class MyDocument extends Document {
  static async getInitialProps(
    ctx: DocumentContext
  ): Promise<DocumentInitialProps> {
    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() { /** */ }
}

next.js/_document.tsx at canary · vercel/next.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

 

2️⃣ Next.js v12 이상

Next.js v12 μ΄μƒλΆ€ν„°λŠ” swc λ₯Ό 톡해 JS λ²ˆλ“€μ„ μ»΄νŒŒμΌν•©λ‹ˆλ‹€.

이 경우 λ³„λ„μ˜ babel μ„€μ • 없이 자체적으둜 styled-component 의 ssr 섀정이 κ°€λŠ₯ν•©λ‹ˆλ‹€.

// next.config.js

const nextConfig = {
  compiler: {
    styledComponents: true,
  },
}

module.exports = nextConfig

 

이후 Next.js v11 κ³Ό λ™μΌν•˜κ²Œ _document.js μ—μ„œ custom Document λ₯Ό μ •μ˜ν•˜κ³ 

getInitialProps λ‚΄μ—μ„œ μŠ€νƒ€μΌ 정보λ₯Ό μ£Όμž…ν•©λ‹ˆλ‹€.

 

// pages/_document.js

import Document, { DocumentContext } from 'next/document'
import { ServerStyleSheet } from 'styled-components'

export default 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() { /** */ }
}

next.js/_document.tsx at canary · vercel/next.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

 

보닀 μžμ„Έν•œ λ‚΄μš©μ€ styled-component λ¬Έμ„œλ₯Ό μ°Έκ³ ν•˜λ©΄ λ©λ‹ˆλ‹€.

styled-components: Advanced Usage

 

styled-components: Advanced Usage

Theming, refs, Security, Existing CSS, Tagged Template Literals, Server-Side Rendering and Style Objects

styled-components.com

 

πŸ“Œ μ°Έκ³ μžλ£Œ

Server-side rendered styled-components with Nextjs

Advanced Features: Custom Document | Next.js

λ°˜μ‘ν˜•

πŸ’¬ λŒ“κΈ€