Next.js에 styled-components 사용하기

Next.js에 styled-components 사용하기

next.js에 styled-components를 사용하기에 앞서 두가지 모듈을 설치해줘야 한다.

설치

npm i styled-components

npm i styled-reset

내가 원하는 스타일로 화면을 그리고 싶다면

일단 브라우저가 기본적으로 가지고 있는 스타일을 리셋해주고 원하는 스타일을 입혀야 한다.

브라우저 기본 스타일을 리셋시켜주는 편한 라이브러리가 있다. style-reset

${reset} 으로 초기화 시키고 공통 css 설정을 해준다.

아래 코드는 내가 스타일을 초기화 하기 위해 기본적으로 사용하는 코드이다.

아래의 기본적인 설정에 프로젝트별로 추가되는 공통 css를 여기에 설정한다.

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

export const GlobalStyle = createGlobalStyle`
  ${reset}
  /* CSS 초기화 */
  * {
    box-sizing: border-box;
    word-break: keep-all;
    -ms-overflow-style: none; /* IE and Edge */
    scrollbar-width: none; /* Firefox */
    &::-webkit-scrollbar {
      display: none; /* Chrome, Safari, Opera*/
    }
  }
  html {
    width: 100%;
    overflow-x: hidden;
  }
  body {
    min-width: 360px;
    overflow-x: hidden;
    line-height: 1.25;
    background: #fff;
  }
  html, body, div, span, applet, object, iframe,
  h1, h2, h3, h4, h5, h6, p, blockquote, pre,
  a, abbr, acronym, address, big, cite, code,
  del, dfn, em, img, ins, kbd, q, s, samp,
  small, strike, strong, sub, sup, tt, var,
  b, u, i, center,
  dl, dt, dd, ol, ul, li,
  fieldset, form, label, legend,
  table, caption, tbody, tfoot, thead, tr, th, td,
  article, aside, canvas, details, embed,
  figure, figcaption, footer, header, hgroup,
  menu, nav, output, ruby, section, summary,
  time, mark, audio, video, main {
    margin: 0;
    padding: 0;
    border: 0;
    font: inherit;
    font-family:'Noto Sans KR', sans-serif;
    vertical-align: baseline;
  }
  /* HTML5 display-role reset for older browsers */
  article, aside, details, figcaption, figure,
  footer, header, hgroup, menu, nav, section, main {
    display: block;
  }
  ol, ul {
    list-style: none;
  }
  blockquote, q {
    quotes: none;
  }
  blockquote:before, blockquote:after,
  q:before, q:after {
    content: '';
  }
  table {
    border-collapse: collapse;
    border-spacing: 0;
  }
  /* button, input, select, textarea 초기화 */
  button, input, select, textarea{
    padding:0;
    margin:0;
    border-radius:0;
    vertical-align:middle;
    font-size:100%;
    font-family: 'Noto Sans KR', sans-serif;
    background:transparent;
  }
  button,
  input[type="button"] {
    border:0 none;
  }
  button{
    cursor:pointer
  }
  button:focus {
    outline: none;
  }
  /* input 기본 스타일 초기화 */
  input {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
  }
  input:focus {
    outline: none;
  }
  /* IE10 이상에서 input box 에 추가된 지우기 버튼 제거 */
  input::-ms-clear { display: none; }
  /* input type number 에서 화살표 제거 */
  input[type=number]::-webkit-inner-spin-button,
  input[type=number]::-webkit-outer-spin-button {
    -webkit-appearance: none;
    -moz-appearance: none;
    appearance: none;
  }
  textarea {
    resize: none;
    -webkit-appearance: none;
  }
  a {
    color: #fff;
    text-decoration: none;
    outline: none;
    &:hover, &:active {
      text-decoration: none;
      color:#fff;
    }
  }
  b {
    font-weight:700;
  }
`;

주의할 점

next.js는 최초 SSR 이후 내부 라우팅을 통해 페이지가 이동되면서 CSR을 하게되는데

이때, 서버에서 생성하는 해시값과 브라우저에서 생성하는 해시값이 서로 달라서 styled-components의 클래스명이 매치가 안되는 문제가 발생하게 된다.

이를 해결하기 위한 방법으로는 두가지가 있는 데

  1. next.js 에서 서버사이드 렌더링시 호출되는 _document.js에 넣어주고 관련 babel plugin을 설치해서 설정해주는 것
  2. 마운트가 된 이후 styled-component가 렌더링 되게 해주는 것.

1번은 이렇게 설정해도 tab 관련 라이브러리 등을 사용하게 되면 또 이슈가 발생한다.

그래서 1번으로 설정하여 사용하다가 이슈 부분을 마운트 이후 렌더링 되게 수정하는 일이 발생했었다.

1번 방법

  1. _document.js에 styled-components의 스타일 코드를 적용하는 로직을 추가한다

공식문서 예시 _document.js

import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'

export default class MyDocument extends Document {
  static async getInitialProps(ctx) {
    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()
    }
  }
}
  1. babel-plugin-styled-components 설치

npm i -D babel-plugin-styled-components

  1. root path에 .babelrc 파일을 생성해 아래 설정을 추가한다.
{
  "presets": ["next/babel"],
  "plugins": [["styled-components", { "ssr": true, "displayName": true, "preprocess": false }]]
}

2번 방법

hook을 사용해서 마운트 된 이후 styled-component가 그려지게 한다.

import React, { useEffect, useState } from 'react';
import { AppProps } from 'next/app';
import { GlobalStyle } from '../styles/grobal-style';
import i18n from '@i18n';
import { I18nextProvider } from 'react-i18next';

function MyApp({ Component, pageProps }: AppProps) {

  const [isMounted, setIsMounted] = useState<boolean>(false);

  useEffect(() => {
    setIsMounted(true);
  }, []);

  return (
    <>
      {isMounted && (
        <I18nextProvider i18n={i18n}>
          <GlobalStyle />
          <Component {...pageProps} />
        </I18nextProvider>
      )}
    </>
  );
}
export default MyApp;

3번 방법

_document.tsx를 사용하지 않고 _app.js에서 probider를 사용해서 theme 등 styled-components가 작동하도록 한다.

global-style도 이 파일에서 공통으로 적용되도록 적용한다.

import { ThemeProvider } from 'styled-components';
import { GlobalStyle } from '@styles/global-style';

...

  return (
    <>
      <Head>
        <title>...</title>
        <link rel="icon" href="/favicon.ico" />
        <meta
          name="..."
          content="..."
        />
      </Head>
...
      <ThemeProvider theme={theme}>
        <GlobalStyle />
        <Fonts />
        ...
      </ThemeProvider>
...
    </>
  );
}
...

Discussion and feedback