Next + TS + styled-components 초기 설정하기

1. nexts.js + ts로 프로젝트 생성

터미널에 다음과 같이 입력합니다.

yarn create next-app --ts

2. styled-components 관련 패키지 다운로드

yarn add styled-components
yarn add -D @types/styled-components babel-plugin-styled-components

3. _document.tsx 생성 및 설정

pages 폴더 안에 _document.tsx를 생성한 뒤 다음의 코드를 삽입합니다.

import Document, {
  Html,
  Head,
  Main,
  NextScript,
  DocumentContext,
} from "next/document";
import { ServerStyleSheet } from "styled-components";

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

  render() {
    return (
      <Html>
        <Head></Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

SSR에서 styled-component를 사용할 경우 커스터마이징이 필요합니다.
위의 코드를 추가해야 SSR 시에 styled가 헤더에 주입되서 스타일이 적용됩니다.

추가해주지 않으면 CSS가 적용되지 않고 먼저 렌더링되는 현상이 발생합니다.

4. _app.tsx 코드 수정

기존에 있는 _app.tsx를 다음과 같이 수정합니다.

import type { AppProps } from "next/app";
import { ThemeProvider } from "styled-components";
import { createGlobalStyle } from "styled-components";

const GlobalStyles = createGlobalStyle`
    * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
    }
    html {
        font-size: 62.5%;
        scroll-behavior: smooth;
        overflow-x: hidden;
    }
    
    body {
        font-size: 1.6rem;
        font-family: 'Inter', 'sans-serif';
        user-select: none;
        -webkit-user-select: none; 
        -moz-user-select: none;   
        -ms-user-select: none;     
        -o-user-select: none;
    }
    
    a {
        text-decoration: none;
        color:black;
    }
    ul {
        list-style-type: none;
    }
    img{
      -webkit-touch-callout: none;
      -webkit-user-select: none;
      -khtml-user-select: none;
      -moz-user-select: none;
      -ms-user-select: none;
      user-select: none;
    }
    button{
      border:none;
      cursor:pointer;
      
      &:focus{
        outline:none;
      }
    }
    input {
      outline:none;
      padding: 0 1.5rem;
      &:focus::placeholder{
        color:transparent;
      }
    }
  `;

const theme = {
  colors: {
    //@ common style
    mainColor: "#FF6363",
    pointColor: "#304ffd",
    lightblue: "#C5E2EE",
    starColor: "#fd4",
  },
};

export type Theme = typeof theme;

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <ThemeProvider theme={theme}>
      <GlobalStyles />
      <Component {...pageProps} />
    </ThemeProvider>
  );
}
export default MyApp;

이 파일에서 GlobalStyles와 theme를 주입할 수 있습니다.
이 부분은 React 어플리케이션에서 styled-components를 사용할 때도 동일하게 추가하는 부분입니다.

GlobalStyles와 theme 안의 값들은 본인이 쓰는 스타일에 맞게 설정하면 됩니다.

5. .babelrc 파일 생성 및 설정

root 위치에서 .babelrc라는 이름의 파일을 생성하고 안에 다음의 코드를 삽입합니다.

{
  "presets": ["next/babel"],
  "plugins": [["babel-plugin-styled-components"]]
}

babel-plugin-styled-components를 플러그인으로 설정해줘야

Warning: Prop `className` did not match. Server: "sc-gsDKAQ bJpAhU" Client: "sc-bdvvtL bulXLB"

위와 같은 경고가 발생하지 않습니다.

위의 에러는 첫 페이지는 SSR로 작동하고 그 이후는 CSR로 화면을 렌더링하는 Next의 특성 때문에 발생합니다.

styled-components는 렌더링시 컴포넌트명에 해시값이 추가되어 고유한 값을 만들면서 class에 추가됩니다.
이때, 서버에서 받은 컴포넌트명 + 해시와 이후 클라이언트에서 작동하는 컴포넌트명 + 해시가 달라지면서 스타일을 불러올수 없는 문제가 발생합니다.

이 문제를 babel-plugin-styled-components 플러그인이 해결해줍니다.

6. styled-components가 잘 적용되는지 확인

이 단계까지 오셨다면 next.js, typescript, styled-component를 함께 사용할 수 있는 설정을 마치셨습니다.

잘 되는지 확인하기 위해 pages 폴더 안에 index.tsx에서 styled-components를 사용해보았습니다.

import type { NextPage } from "next";
import styled from "styled-components";

const Container = styled.div`
  color: ${({ theme: { colors } }) => colors.pointColor};
  text-align: center;
  font-size: 24px;
`;

const Home: NextPage = () => {
  return <Container>Home</Container>;
};

export default Home;

잘 적용되는 것을 볼 수 있습니다!

옵션) svg 로더 추가

웹 어플리케이션을 개발하다보면 여러 이미지, 아이콘 등이 들어가게 되는데 svg는 높은 확률로 1번 이상 들어갑니다.
그래서 나중에 svg가 들어가는 경우를 위해 미리 웹팩 설정을 추가해줍니다.

next.config.js 파일을

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  webpack: (config) => {
    config.module.rules.push({
      test: /\.svg$/i,
      issuer: /\.[jt]sx?$/,
      use: ["@svgr/webpack"],
    });
    return config;
  },
};

module.exports = nextConfig;

다음과 같이 수정해주면 됩니다.

참고

https://velog.io/@danmin20/Next.js-Typescript-Styled-component-쉽게-구축하기

https://velog.io/@hwang-eunji/Styled-components-nextjs에서-className-오류

좋은 웹페이지 즐겨찾기