Next 13 에서의 CSS-in-JS with. styled-components

신대현·2022년 11월 21일
8

NextJS

목록 보기
1/2
post-thumbnail

❗️베타버전의 문서를 보고 작성했고 아직 실제 서비스 환경에서는 사용하지 않는 것이 좋습니다.
잘못된 부분이 있을 경우 피드백 부탁드립니다

테스트 환경
next 13.0.3
react 18.2.0
react-dom 18.2.0
styled-components 6.0.0-beta.6

최근에 Next의 메이저 버전이 업데이트되면서 app 이라는 디렉토리가 생기며 많은 변화가 생겼습니다 그중에서 현재 회사에서 사용 중인 게 styled-components 이고 마침 app 디렉토리에서 styled-components을 지원하기 때문에 어떻게 바뀌었는지 알아보겠습니다.

현재 Next 13에서의 CSS-in-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, 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.

If you want to style Server Components, we recommend using CSS Modules or other solutions that output CSS files, like PostCSS or Tailwind CSS.

구글 번역)
경고: 런타임 JavaScript가 필요한 CSS-in-JS 라이브러리는 현재 서버 구성 요소에서 지원되지 않습니다. 서버 구성 요소 및 스트리밍과 같은 새로운 리액트 기능과 함께 CSS-in-JS를 사용하려면 라이브러리 작성자가 동시 렌더링을 포함한 최신 리액트 버전을 지원해야 한다.

우리는 React 서버 구성 요소 및 스트리밍 아키텍처를 지원하는 CSS 및 JavaScript 자산을 처리하기 위해 업스트림 API에 대해 React 팀과 협력하고 있습니다

서버 구성 요소의 스타일을 지정하려면 CSS 모듈 또는 PostCSS 또는 Tailwind CSS와 같은 CSS 파일을 출력하는 다른 솔루션을 사용하는 것이 좋습니다.

Server Components 스타일을 지정하려면 CSS Modules또는 PostCSS 
또는 Tailwind CSS와 같은 CSS 파일을 출력하는 다른 솔루션을 사용하는 
것이 좋습니다.

라고 설명하고 있습니다.

현재 APP 디렉토리에서 지원되는 라이브러리는 아래와 같습니다.
styled-jsx
styled-components

Configuring CSS-in-JS in app

기존의 Styled-components Server rendering 셋팅

// _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()
    }
  }
}

next 13 버전부터 app 디렉토리에서는 _app.tsx , _documents.tsx 는 사용하지 않기 때문에 위의 방식대로는 사용하질 못합니다.

app 디렉토리에서 SSR에서 styled-components을 사용하기 위해선 아래와 같은 코드를 작성해 주어야 합니다 참고

// /lib/styled-components.tsx

1. styled-components API를 사용하여 렌더링 중에 
생성된 모든 CSS 스타일 규칙과 해당 규칙을 반환하는 
함수를 수집하는 전역 레지스트리 구성 요소를 만듭니다

import React from 'react';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';

export function useStyledComponentsRegistry() {
  const [styledComponentsStyleSheet] = React.useState(
    () => new ServerStyleSheet(),
  );

  const styledComponentsFlushEffect = () => {
    const styles = styledComponentsStyleSheet.getStyleElement();
    styledComponentsStyleSheet.instance.clearTag();
    return <>{styles}</>;
  };

  const StyledComponentsRegistry = ({
    children,
  }: {
    children: React.ReactNode;
  }) => (
    <StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
      {children as React.ReactElement}
    </StyleSheetManager>
  );

  return [StyledComponentsRegistry, styledComponentsFlushEffect] as const;
}
// /lib/RootStyleRegistry.tsx
2. useServerInsertedHTML 후크를 사용하여
레지스트리에 수집된 스타일을 루트 레이아웃의 <head> 
HTML 태그에 삽입하는 클라이언트 구성 요소를 만듭니다.

"use client";

import { useServerInsertedHTML } from "next/navigation";

import { useStyledComponentsRegistry } from "~/lib/styled-components";

export default function RootStyleRegistry({ children }: { children: React.ReactNode }) {
  const [StyledComponentsRegistry, styledComponentsFlushEffect] = useStyledComponentsRegistry();

  useServerInsertedHTML(() => <>{styledComponentsFlushEffect()}</>);

  return <StyledComponentsRegistry>{children}</StyledComponentsRegistry>;
}
// /app/layout.tsx
import RootStyleRegistry from "~/lib/RootStyleRegistry";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <RootStyleRegistry>{children}</RootStyleRegistry>
      </body>
    </html>
  );
}
// /app/page.tsx
import styled from "styled-components";

function HomePage() {
  return <Container>Client Components</Container>;
}
export default HomePage;
const Container = styled.div`
  padding: 0 2rem;
`;

이렇게 세팅 후 styled-components 사용 시 바로 아래의 에러를 만날 것입니다🥲
공식 홈페이지를 찾아보니 styled-components ,외부 라이브러리 등을 사용 시에는 clinet components 을 사용하는 게 맞을 거 같습니다

To simplify the decision between Server and Client Components, we recommend using Server Components (default in the app directory) until you have a need to use a Client Component.

서버와 클라이언트 구성 요소 간의 결정을 단순화하려면 클라이언트 구성 요소를 사용해야 할 때까지 서버 구성 요소(앱 디렉터리의 기본값)를 사용하는 것이 좋습니다.
참고

Third-party packages
구글 번역) 오늘날 클라이언트 전용 기능을 사용하는 npm 패키지의 많은 구성 요소에는 아직 명령어가 없습니다. 이러한 타사 구성요소는 자체적으로 "클라이언트 사용" 지침이 있기 때문에 사용자의 클라이언트 구성요소 내에서는 예상대로 작동하지만 서버 구성요소 내에서는 작동하지 않습니다.

현재 컴포넌트 작성 시 서버 컴포넌트가 default 이기 때문에
"use client" 추가해서 client components 라는걸 알려주어야 합니다.

"use client"; // Use Client Components

import styled from "styled-components";

function HomePage() {
  return <Container>Client Components</Container>;
}
export default HomePage;
const Container = styled.div`
  padding: 0 2rem;
`;

끝 😀

Good to know:

구글 번역)
서버 렌더링 중에 스타일이 전역 레지스트리로 추출되고 HTML의 로 플러시됩니다. 이렇게 하면 스타일 규칙을 사용할 수 있는 콘텐츠 앞에 배치됩니다. 앞으로 우리는 다가올 React 기능을 사용하여 스타일을 주입할 위치를 결정할 수 있습니다

스트리밍하는 동안 각 청크의 스타일이 수집되어 기존 스타일에 추가됩니다. 클라이언트측 수화가 완료되면 styled-component가 평소와 같이 인계되어 추가 동적 스타일을 주입합니다.

이 방법으로 CSS 규칙을 추출하는 것이 더 효율적이기 때문에 특히 스타일 레지스트리에 대한 트리의 최상위 수준에서 클라이언트 구성 요소를 사용합니다. 후속 서버 렌더링에서 스타일을 다시 생성하는 것을 방지하고 서버 구성 요소 페이로드에서 스타일이 전송되는 것을 방지합니다.

참고

베타 공식 문서

profile
프론트엔드 개발자 입니다

3개의 댓글

comment-user-thumbnail
2022년 11월 27일

굉장히 유용한 글입니다!

답글 달기
comment-user-thumbnail
2023년 1월 31일

공식 문서의 example과 올려주신 코드가 살짝 다른데 어떤 것을 수정하신건가요?

1개의 답글