Javascript/NextJS

[NextJS] Next에서의 CSS in JS

  • -
728x90

평소 리액트를 사용하면서 Styled-Components를 주로 사용해왔다. css를 사용하면 class name의 중복을 고려하며 개발 해야하고, 특정 스타일 코드를 찾기 어려운 단점 때문이었다. 

 

CSS in JS

CSS-in-js는 자바스크립트 코드로 CSS를 작성하는 방식이다. 2014년 처음 나오게 됐고, 다음과 같은 문제를 해결하기 위해 나온 기술이다.

  • Global namespace: 글로벌 공간에 선언된 이름의 명명 규칙 필요
  • Dependencies: CSS간의 의존 관계를 관리
  • Dead Code Elimination: 미사용 코드 검출
  • Minification: 클래스 이름의 최소화
  • Sharing Constants: JS와 CSS의 상태 공유
  • Non-deterministic Resolution: CSS 로드 우선 순위 이슈
  • Isolation: CSS와 JS의 상속에 따른 격리 필요 이슈

이 방법을 사용하기 위해 여러 라이브러리가 출시 되었고, 대표적으로는 styled-components, emotion 등의 라이브러리가 있다.

기존 웹사이트는 HTML, CSS, JavaScript를 각자 별도의 파일로 두었는데, React나 Vue, Angluar와 같은 자바스크립트 라이브러리가 인기를 끌고 컴포넌트 기반 개발 방법이 주류가 됨에 따라 한 컴포넌트에 HTML, CSS, JavaScript를 모두 포함하는 패턴이 많이 사용되었다.

 

브라우저가 렌더링되는 과정을 생각해보자.

  • 브라우저는 HTML 문서를 파싱하고 DOM 트리를 구축한다
  • CSS 파일을 다운로드하고 파싱하여 CSSOM 트리를 생성한다.
  • DOM과 CSSOM을 결합하여 렌더 트리를 생성하고, 이를 기반으로 브라우저에서 웹 페이지를 렌더링한다.
  • 일반적으로 `<script>` 태그로 연결된 JavaScript 파일은 파싱을 차단하며, HTML 파싱 이후 순차적으로 로드 및 실행된다.
  • `<link>` 태그로 연결된 CSS 파일은 파싱을 차단하지 않고 병렬로 로드되며, HTML 파싱 중에 로드된다.

위 과정을 생각해보면 CSS-in-JS로 구성된 라이브러리들은 HTML과 CSS 처리가 끝나고 JS파일을 로드할 시점에 처리될것이다. 성능상으로만 생각해보면 사실 CSS-in-JS 방식이 더 좋아보이지 않는다. 하지만, DX와 생산성을 올리는데에 있어서는 CSS-in-JS가 편하고 그래서 한동안 인기를 끌엇던게 아닐까 생각한다.

 

NextJS에서의 CSS-in-JS

NextJS의 최대 장점 중 하나가 pre-rendering인데, styled-components는 js파일에 css 문법을 담은 것이기 때문에 이것이 제대로 작동할까라는 의문이 생겼다.

 

https://nextjs.org/docs/app/building-your-application/styling/css-in-js

 

Styling: CSS-in-JS | Next.js

Warning: CSS-in-JS libraries which require runtime JavaScript are not currently supported in Server Components. Using CSS-in-JS with newer React features like Server Components and Streaming requires library authors to support the latest version of React,

nextjs.org

 

역시나 공식문서에서 이에관한 문제에 대한 설명을 잘 해주고있다.

Warning: CSS-in-JS libraries which require runtime JavaScript are not currently supported in Server Components. Using CSS-in-JS with newer React features like Server Components and Streaming requires library authors to support the latest version of React, including concurrent rendering. We're working with the React team on upstream APIs to handle CSS and JavaScript assets with support for React Server Components and streaming architecture.

구글 번역

경고: 런타임 자바스크립트가 필요한 CSS-in-JS 라이브러리는 현재 서버 구성 요소에서 지원되지 않습니다. 서버 구성 요소 및 스트리밍과 같은 새로운 리액트 기능과 함께 CSS-in-JS를 사용하려면 라이브러리 작성자가 동시 렌더링을 포함한 최신 버전의 리액트를 지원해야 합니다. 우리는 리액트 서버 구성 요소 및 스트리밍 아키텍처를 지원하여 CSS 및 자바스크립트 자산을 처리하기 위해 업스트림 API에 대한 리액트 팀과 협력하고 있습니다.

 

 

~ Next 12 버전 

// _document.tsx
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()
    }
  }
}

13 이전 버전의 Next 에서는 _documents.tsx 파일에서 위와 같은 설정을 해주어야 했다. SSR로 작동하는 Next이기 때문에, 서버에서 렌더링한 페이지에 style을 적용시켜주는 과정인듯하다.

 

13 버전 ~

'use client'
 
import React, { useState } from 'react'
import { useServerInsertedHTML } from 'next/navigation'
import { ServerStyleSheet, StyleSheetManager } from 'styled-components'
 
export default function StyledComponentsRegistry({
  children,
}: {
  children: React.ReactNode
}) {
  // Only create stylesheet once with lazy initial state
  // x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
  const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet())
 
  useServerInsertedHTML(() => {
    const styles = styledComponentsStyleSheet.getStyleElement()
    styledComponentsStyleSheet.instance.clearTag()
    return <>{styles}</>
  })
 
  if (typeof window !== 'undefined') return <>{children}</>
 
  return (
    <StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
      {children}
    </StyleSheetManager>
  )
}
import StyledComponentsRegistry from './lib/registry'
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <body>
        <StyledComponentsRegistry>{children}</StyledComponentsRegistry>
      </body>
    </html>
  )
}

 

위도 마찬가지로 JS로 작성된 style을 적용시켜주는 과정이다. 12버전에서의 코드보다 13버전에서의 코드가 훨씬 직관적인듯하다.

 

styled-components를 사용하기 위해서는 클라이언트 구성 요소에서 처리를 해야하므로 use client를 명시해주고, 그 후 마치 Provider와 비슷하게 Registry라는 컴포넌트로 하위 컴포넌트에서 styled components가 모두 적용될 수 있게 하는 방식이다.

 

 

공식문서에 styled-components 외에도 많은 CSS-in-JS에 대한 예제 코드가 잘 명시되어있다. 

 

무조건적으로 SSR이 더 좋고, CSS-in-JS가 나쁘다라는 것은 아니지만, Next를 사용함에 있어서는 더 좋은 방식이 있고, 그 좋은 방식을 도입하는데에 있어서 얼마나 더 효율적인가를 생각하면서 프로젝트를 시작하는게 꽤나 흥미롭다. 이런저런 자료들을 조사하면서 새롭게 알게되는 내용들도 많고, 잘못알고있던것들 무지성으로 사용하던 것들 이런 지식을 습득하는게 좋은 것같다.

728x90
300x250
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.