[Next.js] 13버전 styled-component GlobalStyle 적용

yesong·2023년 8월 23일

nextjs

목록 보기
5/5

nextjs 이전 버전에서는 _app과 _document 파일에 GlobalStyle을 적용해주면 됐는데, nextjs13 버전에서는 _app과 _document파일을 사용하지 않아서 새롭게 GlobalStyle을 적용해주는 방법을 찾아보았다.

1. GlobalStyle 파일 생성

createGlobalStyle을 이용해서 GlobalStyle 객체를 생성한다. 이곳에 적용할 global style을 다 넣어주면 된다. 나는 styled-component가 지원하는 styled-reset을 불러와서 내가 정의하고자 하는 global style과 함께 넣어주었다.
그리고 폴더 구조는 개개인에 따라 다른데, 내 경우에는 style 폴더를 앱 폴더와 분리하는 것을 선호해서 app폴더와 따로 styles 폴더를 만들었다. 내 스케폴딩은 다음과 같다.

├── node_modules
├── public
├── src
│   ├──app
│   │   ├──layout.tsx
│   │   ├──page.tsx
│   ├──components
│   ├──styles
│   │   ├──global-style.ts
│   │   ├──registry.tsx
├── eslint.json
├── .gitignore
├── next.config.js
├── ...
// src/styles/global-styles.ts

import { createGlobalStyle } from "styled-components";
import { reset } from "styled-reset";

export const GlobalStyle = createGlobalStyle`
  ${reset}
  * {
    box-sizing: border-box;
  }

  html, body { 
    width: 100%;
    height: 100%;
    min-width: 1200px;
    background-color: #ffffff;
    font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI;
    font-size : 16px;
    color: rgb(58, 58, 58);
  }

  ul, ol {
    list-style: none;
  } 

  button {
    all: unset;
    cursor: pointer;
  }
`;

2. SSR 및 CSR 컴포넌트 간 일관된 스타일 유지

Next.js와 같은 서버 사이드 렌더링 애플리케이션에서 styled-components를 사용할 때 스타일이 클라이언트 측에서 React 애플리케이션이 다시 로드될 때 일관되게 유지되어야 하는 문제가 있다. 이를 위해 styled-components에서 제공하는 StyleSheetManager 컴포넌트를 사용할 수 있다.

기존 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는 사용하지 않기 때문에 위 방식으로는 불가능하다.
따라서 Next.js 공식문서에서는 다음과 같은 방법을 제안한다. StyleSheetManager와 같은 styled-components API를 사용하여 렌더링 중 사용되는 모든 CSS rule을 모아놓을 global registry 컴포넌트를 만들고, useServerInsertedHTML함수를 사용해서 해당 style을 root의 layout 파일의 head 태그 안에 주입하도록 한다.

First, use the styled-components API to create a global registry component to collect all CSS style rules generated during a render, and a function to return those rules. Then use the useServerInsertedHTML hook to inject the styles collected in the registry into the head HTML tag in the root layout.

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

다음과 같이 위 코드에서 정의한 style registry파일의 StyledComponentsRegistry 컴포넌트를 불러와 body 태그 아래에 넣어주면 글로벌 스타일 적용이 완성된다.

// app/layout.tsx

import StyledComponentsRegistry from './lib/registry';

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

2-1. 나의 파일구조로 SSR 적용

위와 동일한 로직으로 내 파일구조에 맞게 global style을 적용시켰다.

// src/styles/registry.tsx

"use client"

import { useServerInsertedHTML } from "next/navigation"
import React, { useState } from "react"
import { ServerStyleSheet, StyleSheetManager } from "styled-components"
import { GlobalStyle } from '@/styles/global-style';

export const Registry = ({ children }: { children: React.ReactNode }) => {
  const [sheet] = useState(() => new ServerStyleSheet())

  useServerInsertedHTML(() => {
    const styles = sheet.getStyleElement() // style 태그를 돔에 생성하기 위한 리엑트 엘리먼트들을 배열형태로 반환합니다.
    sheet.instance.clearTag()
    return <>{styles}</>
  })

  if (typeof document !== "undefined") {
    return <>{children}</>
  }

  return (
    <StyleSheetManager sheet={sheet.instance}>
        <GlobalStyle />
        {children}
    </StyleSheetManager>
  )
}
// src/app/layout.tsx

'use client';
import { Registry as StyledComponentRegistry } from "@/styles/registry"
import { QueryClient, QueryClientProvider } from 'react-query';
import type { Metadata } from 'next';
import { Inter } from 'next/font/google';

const inter = Inter({ subsets: ['latin'] });

const queryClient = new QueryClient();

export const metadata: Metadata = {
  title: '서랍',
  description: 'Generated by create next app',
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang='ko'>
      <body className={inter.className}>
        <QueryClientProvider client={queryClient}>
          <StyledComponentRegistry>{children}</StyledComponentRegistry>
        </QueryClientProvider>
      </body>
    </html>
  );
}

참고링크

https://wikidocs.net/197760
https://velog.io/@jay/Next.js-13-master-course-Styling
https://nextjs.org/docs/app/building-your-application/styling/css-in-js

profile
접근성을 고민하는 개발자

0개의 댓글