Next.js + Typescript + Styled-components 세팅에 관한 모든 것

danmin20·2021년 2월 21일
1

Web

목록 보기
1/1

웹팩을 일일히 세팅하거나, 가벼운 프로젝트는 CRA로 구축하거나 하던 중, next.js를 알게되었고 그렇게 대부분의 프로젝트는 next.js를 도입하여 구축하고 있다🙃
커스텀의 자유도가 높다는 것이 장점인 만큼, 정말 많은 삽질을 한 것 같다.
지금도 계속해서 삽질 중이며, 하나씩 발견할 때마다 글을 업데이트 할 예정이다🙂

next 프로젝트 기본 폴더 구조

root                        			 
│
├── components                    // 컴포넌트들                
│				
├── pages                         // 페이지를 담당하는 컴포넌트
│     ├── index.tsx               // '/'
│     ├── _app.tsx            
│     ├── _document.tsx      
│     ├── index.scss              // 하나의 최상단 css를 가져야 함
│ 
├── public  				      // svg & json
│
└── styles                          
       ├── global-style.ts        // 글로벌 스타일 적용
       ├── styled.d.ts            // 정의한 theme 타입 정의
       └── styled.d.ts            // theme 및 미디어쿼리 정의

next.jsautomatic routingautomatic splitting을 지원하기 때문에, pages 폴더 안의 파일들을 기준으로 자동적으로 라우팅을 해 준다.

기본적으로 사용하는 라이브러리

"dependencies": {
    "@svgr/webpack": "^5.5.0",
    "babel-plugin-styled-components": "^1.12.0",
    "next": "^10.0.6",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "sass": "^1.32.8",
    "styled-components": "^5.2.1",
    "styled-reset": "^4.3.4"
  },
  "devDependencies": {
    "@next/bundle-analyzer": "^10.0.7",
    "@types/node": "^14.14.25",
    "@types/react": "^17.0.1",
    "@types/styled-components": "^5.1.7",
    "typescript": "^4.1.5",
    "@typescript-eslint/eslint-plugin": "^4.14.1",
    "@typescript-eslint/eslint-plugin-tslint": "^4.14.1",
    "@typescript-eslint/parser": "^4.14.1",
    "eslint": "^7.18.0",
    "eslint-config-airbnb": "^18.2.1",
    "eslint-plugin-import": "^2.22.1",
    "eslint-plugin-jsx-a11y": "^6.4.1",
    "eslint-plugin-react": "^7.22.0",
    "eslint-plugin-react-hooks": "^4.2.0"
  }

svg loader

next.config.js

/* eslint-disable @typescript-eslint/no-var-requires */
const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true',
});

module.exports = withBundleAnalyzer({
  target: 'serverless',

  webpack(conf) {
    conf.module.rules.push({
      test: /\.svg$/,
      use: [
        {
          loader: '@svgr/webpack',
          options: {
            svgoConfig: {
              plugins: [{
                removeRasterImages: false,
                removeStyleElement: false,
                removeUnknownsAndDefaults: false,
              }],
            },
          },
        },
      ],
    });

    return conf;
  },
});

next.js에서는 svg로더를 기본적으로 제공해주지 않기 때문에, @svgr/webpack을 이용하여 설정해주었다.
svg를 img태그가 아닌, 리액트 컴포넌트로 사용할 경우 opacity가 이상해지는 피그마 상의 문제점이 있었는데, 옵션을 추가하여 해결하였다. (removeUnknownsAndDefaults만 넣어주어도 된다.)
(withBundleAnalyzer은 번들링 이후의 페이지 사이즈 측정 등을 위함이다.)

styled-components

global-style.ts

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

export const GlobalStyle = createGlobalStyle`
    ${reset}
    html{
        ...
    }
`;

styled.d.ts

import 'styled-components';

declare module 'styled-components' {
  export interface DefaultTheme {
    color: {
      ...
    };
  }
}

theme.ts

import { DefaultTheme } from 'styled-components';

export const theme: DefaultTheme = {
  color: {
    ...
  },
};

const customMediaQuery = (maxWidth: number): string => `@media (max-width: ${maxWidth}px)`;

export const media = {
  // 미디어 쿼리 정의
  ...
};

defaultTheme 내부는 똑같이 작성해 주면 된다.

_app.tsx

import type { AppProps } from 'next/app';
import { ThemeProvider } from 'styled-components';
import { theme } from '../styles/theme';
import './index.scss';
import { GlobalStyle } from '../styles/global-style';

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

export default MyApp;

_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>
          <meta charSet="utf-8" />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

next.js에서 styled-components 사용시, styled-components가 SSR된 styles를 안에 inject시킬 수 있게 끔 설정해주어야 한다.
(참고 : https://styled-components.com/docs/advanced#nextjs)

babel 설정

next.js에서 styled-components를 적용하면, className이 다르다는 내용의 에러가 발생한다.

next.js는 초기 렌더링만 서버가 담당(SSR)하고 그 이후에는 서버를 거치지 않은 채 내부 라우팅을 이용해 페이지가 이동되면서 브라우저에서 렌더링(CSR)을 하게 되는데,
첫 화면 로딩시에는 SSR로 렌더링하면서 오류가 발생하지 않지만 그 이후 부터는 CSR로 렌더링하면서, 서버에서의 클래스명과 클라이언트에서 클래스명이 달라져서 발생하는 에러이다.

.babelrc

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

0개의 댓글