본문 바로가기
Web/Next.js

Next.js 13 pages 라우터에서 app router로 마이그레이션하기 - style-components

by 김첨지 2023. 2. 6.

 

ssr방식에서 style-components

기존 pages 라우터 방식에서는 server side 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()
    }
  }
}

 
하지만 app 라우터에서는 _app과 _document는 사용하지 않기 때문에 위 방식으로는 불가능하다.
 
이에 따라 공식문서에서는 새로운 방법을 제안하고 있다.

 

// lib/registry.tsx

'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>
  );
}

 

// app/layout.tsx

import StyledComponentsRegistry from './lib/registry';

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        <StyledComponentsRegistry>{children}</StyledComponentsRegistry>
      </body>
    </html>
  );
}

 
위 코드를 그대로 옮겨 적으면 styledComponentsStyleSheet.instance.clearTag()에서 다음과 같은 에러가 나타난다.

'ServerStyleSheet' 형식에 'clearTag' 속성이 없습니다. ts(2339)

 
해당 코드 위에 // @ts-ignore를 추가해 주면 된다. ( https://github.com/vercel/next.js/issues/42526)

 

    // @ts-ignore
    styledComponentsStyleSheet.instance.clearTag();

 
하지만 아래와 같은 에러가 발생했다.

Warning: Prop `className` did not match. Server: "sc-hBxehG bgEGtH" Client: "sc-fnGiBr jejHKZ"

 
https://tesseractjh.tistory.com/164

 

[Next.js] Next.js에서 Prop `className` did not match 경고가 뜨는 이유

이 글은 Prop `props이름` did not match. 로 시작하는 Warning에 관한 내용입니다. 또한 Next.js에서 styled-components를 사용하면서 겪은 여러 가지 문제를 다룹니다. _document.tsx(jsx) Next.js에서 styled-components를

tesseractjh.tistory.com

 
위 블로그를 보고 next.config.js에 아래 코드를 추가해 주었다.

 

// next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  compiler: {
    styledComponents: true,
  },
};

module.exports = nextConfig;

 
만약 swc를 사용하지 않고 babel을 사용하는 경우에는 위 블로그 참고하면 된다.
 
 
이번에는 터미널에서 에러가 나타났다.

 

Error: React.Children.only expected to receive a single React element child.

 
<StyledComponentsRegistry> 안에 여러 children이 있는 것이 의심되었고, {children} 하나만 남겨보았더니 에러가 더 이상 나타나지 않았다.

<ReactQueryDevtools />을 밖으로 빼 <StyledComponentsRegistry>의 자식이 하나가 되도록 해주었다.
 
// app/layout.tsx

"use client";

import { theme } from "@chooz/ui";
import styled, { ThemeProvider } from "styled-components";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { GlobalStyles } from "styles/globalStyles";
import Header from "components/common/Header";
import StyledComponentsRegistry from "../lib/registry";

function RootLayout({
  // Layouts must accept a children prop.
  // This will be populated with nested layouts or pages
  children,
}: {
  children: React.ReactNode;
}) {
  const queryClient = new QueryClient();

  return (
    <html lang="kr">
      <body>
        <div id="portal" />
        <QueryClientProvider client={queryClient}>
          <ReactQueryDevtools />
          <StyledComponentsRegistry>
            <ThemeProvider theme={theme}>
              <GlobalStyles />
              <div id="stars" />
              <div id="stars2" />
              <div id="stars3" />
              <Applayout>
                <Header />
                {children}
              </Applayout>
            </ThemeProvider>
          </StyledComponentsRegistry>
        </QueryClientProvider>
      </body>
    </html>
  );
}

const Applayout = styled.div`
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  padding: 0 16px;
  flex: 1;
`;

export default RootLayout;

 
 
app 디렉터리에서 스타일 컴포넌트의 서버 사이드 랜더링에 대해서 자세히 알고 싶다면 아래에서 확인하면 된다.
https://beta.nextjs.org/docs/styling/css-in-js#styled-components

 

Styling: CSS-in-JS | Next.js

Learn how to use CSS-in-JS inside the `app` directory.

beta.nextjs.org