[NextJS] styled-components와 함께 사용하기

강은비·2022년 6월 15일
8

NextJS

목록 보기
7/10
post-thumbnail
post-custom-banner

시작하기 전에
NextJS는 기본적으로 페이지를 SSR을 이용해 pre-rendering한다. Server Side에서 html 파일을 구성하여 브라우저 측에 전달해 렌더링한다.(CSS도 렌더링됨) 이후 JS 파일이 로드되어 자바스크립트 코드가 적용된다.

👉 NextJS의 페이지 렌더링 방식 알아보기


💄CSS-in-JS와 Pre-Rendering

자바스크립트 코드가 적용이 되지 않은 페이지가 미리 렌더링되기 때문에 CSS-in-JS로 스타일링을 하면 스타일이 적용되지 않은 html 코드가 먼저 렌더링되는 문제가 발생하게 된다.

NextJS는 이에 대한 해결책을 제시한다. html 파일에 CSS-in-JS 형식으로 작성된 스타일 요소들을 주입시켜서 스타일이 뒤늦게 적용되는 문제를 해결할 수 있다.
👉 NextJS 공식문서 - Customizing 'renderPage'

import Document, { DocumentContext, DocumentInitialProps } from "next/document";
import { ServerStyleSheet } from "styled-components";

export default class MyDocument extends Document {
  static async getInitialProps(
    ctx: DocumentContext
  ): Promise<DocumentInitialProps> {
    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();
    }
  }
}

⚙️ babel 설정

Next.js를 페이지 접속 시 SSR를 이용하고 이후 페이지 이동 시 CSR를 이용하여 페이지를 렌더링한다. 이때 serverclient에서 생성하는 class 해시값이 달라 충돌로 인해 문제가 발생한다.

👀 참고 : styled-component로 만들어진 컴포넌트는 내부적으로 태그의 className해시하여 만들어지게 된다.

👉 babel-plugin-styled-components를 설치하고 .babelrc 파일을 설정하도록 하자~
yarn add babel-plugin-styled-components

{
  "presets": ["next/babel"],
  "plugins": [
    [
      "styled-components",
      {
        "ssr": true,
        "displayName": true,
        "preprocess": true
      }
    ]
  ]
}

🌙 다크모드 기능 구현하기

NextJS를 공부하며 간단한 영화 정보 제공 애플리케이션🎬을 만들었다. 다크모드 기능을 추가했는데 어떻게 구현했는지 정리해보겠다. 😎

// styles/styled.d.ts
// theme 객체의 타입 지정

import "styled-components";

declare module "styled-components" {
  export interface DefaultTheme {
    bgColor: string;
    menuColor: string;
    textColor: string;
    shadowColor: string;
    name: string;
  }
}
// styles/theme.ts
// theme 객체 정의

export const lightTheme = {
  bgColor: "#fff",
  menuColor: "blueviolet",
  textColor: "#000",
  shadowColor: "rgba(0, 0, 0, 0.15)",
  name: "light",
};

export const darkTheme = {
  bgColor: "#252525",
  menuColor: "#D391FA",
  textColor: "#fff",
  shadowColor: "#000",
  name: "dark",
};
// pages/_app.tsx

function MyApp({ Component, pageProps }: AppProps) {
  const [isLightTheme, setIsLightTheme] = useState(true);
  const changeTheme = () => {
    setIsLightTheme(!isLightTheme);
  };
  return (
    <ThemeProvider theme={isLightTheme ? lightTheme : darkTheme}>
      <GlobalStyle />
      <ThemeButton isLightTheme={isLightTheme} changeTheme={changeTheme} />
      <Layout>
        <Component {...pageProps} />
      </Layout>
    </ThemeProvider>
  );
}
post-custom-banner

0개의 댓글