[Styled-Components] Server Side Rendering을 위한 style 추출

서해빈·2021년 4월 26일
3

Trouble

목록 보기
7/15
post-thumbnail

발단 - SSR된 페이지에서 style이 누락됨

현재 개발중인 프로젝트는 React와 Express를 사용하고 있다. 사용자가 처음 접속할 시에는 Express 서버에서 React 앱을 Server Side Rendering한 페이지를 제공하도록 작성했는데, 첫 로드 시에 Styled-Component를 사용한 element들의 style이 누락되는 일이 발생했다.

때문에 처음 약 1초는 순수 html로만 작성된 페이지를 보여주다가, 이 후 client단에서 react로 다시 rendering한 결과물을 보여주는 상황이었다.

원인 분석

Styled-Component는 앱에 처음 import될 때, 자신을 통해 생성된 모든 component의 개수를 세는 내부 카운터 변수를 생성한다. 이 후 Styled-Component를 사용해 새 component를 생성하면 내부 식별자 componentId가 생성되고 내부 카운터의 값이 1 올라간다. componentId는 MurmurHash 알고리즘으로 생성한 hash number를 알파벳 문자로 변경한 값이다.

counter++
const componentId = 'sc-' + hash('sc' + counter)

식별자가 생성되면 styled-components는 <head>에 <style> element를 삽입하고(만약 해당 component가 앱에서 처음 생성된 component이고, element가 아직 DOM에 추가되지 않았다면) componentId가 포함된 주석 마커를 <style> element에 추가한다.

<style data-styled>
  /* sc-component-id: sc-bdVaJa */
</style>

나중에 해당 componentId를 포함한 class name을 가진 css를 이 style element에 추가해준다.

그런데 현재 Express 서버에서는 ReactDOMServer.renderToString()으로 문자열로 생성한 react app을 Pug로 <body> 밑에 추가해 렌더링한 페이지를 제공하고 있다. 때문에 head 밑에 추가되어야 할 <style> element가 누락되어 이런 현상이 발생한 것으로 보인다.

크롬 개발도구에서 서버에서 받은 html의 head에 style이 존재하지 않는 것을 확인할 수 있다.
style 누락 이미지

해결 - ServerStyleSheet

다행히도 Styled-Component는 stylesheet rehydration을 통한 concurrent server side rendering을 지원한다.

Server Side Rendering

styled-components supports concurrent server side rendering, with stylesheet rehydration. The basic idea is that everytime you render your app on the server, you can create a ServerStyleSheet and add a provider to your React tree, that accepts styles via a context API.

사용 예시는 다음과 같다.

import { renderToString } from 'react-dom/server'
import { ServerStyleSheet } from 'styled-components'

const sheet = new ServerStyleSheet() // sheet 생성. 위에서 말하는 provider의 역할을 할 것이다.
try {
  // render과정에서 style들을 수집한다.
  const html = renderToString(sheet.collectStyles(<YourApp />))
  // 수집한 style들을 바탕으로 html의 style 태그를 생성한다.
  const styleTags = sheet.getStyleTags() // or sheet.getStyleElement();
} catch (error) {
  // handle error
  console.error(error)
} finally {
  sheet.seal()
}

렌더링 후 생성된 react app과 style element를 pug의 String Interpolation, Unescaped 기능으로 같이 삽입해주었다. 그 결과 첫 load시에도 style이 누락되지 않고 정상적으로 출력되는 것을 확인했다.

style 추가 이미지

참고 및 출처

  • [Styled-Components Docs] Server Side Rendering - 바로가기
  • [Pug Docs] String Interpolation, Unescaped - 바로가기
  • styled-components는 어떻게 동작할까? - 바로가기
  • How styled-components works: A deep dive under the hood - 바로가기

0개의 댓글