Web/React & Next.js

Next.js | Next.js + Styled-Components 초기 세팅 (next 13)

일렁이는코드 2023. 2. 14. 15:44

styled-components는 CSR로 작동하기 때문에 SSR인 next-js에서는 styled이 적용되도록 따로 설정이 필요합니다. 

(설정을 하지 않으면 스타일이 적용되기 전에 렌더가 되어 화면이 보이는 현상이 나타납니다..🤦🏻‍♀️)

 

1. 설치

npm i styled-components
npm i -D @types/styled-components babel-plugin-styled-components

 

2. next.js에서 사용하도록 설정하기

1) .babelrc 생성

{
  "presets": ["next/babel"],
  "plugins": [
    [
      "styled-components",
      {
        "ssr": true, // SSR을 위한 설정
        "fileName": true, // 코드가 포함된 파일명을 알려줌.
        "displayName": true, // 클래스명에 컴포넌트 이름을 붙임
        "pure": true // 사용되지 않는 속성 제거
      }
    ]
  ]
}

 

2) _document.tsx 파일에 코드 추가 (next.js에서 제시한 예시)

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 />
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

export default MyDocument;

 

 

3.  styled-components 세팅

1) 기본 css 파일 삭제

 

2) styled-components 파일 생성 (src > styles)

npm i styled-reset
// customReset.ts
import { css } from "styled-components";

export const customReset = css`
  *,
  html,
  *:before,
  *:after {
    -moz-box-sizing: border-box;
    -webkit-box-sizing: border-box;
    box-sizing: border-box;
  }

  * {
    outline: none;
    cursor: default;
  }

  *:focus {
    outline: none;
  }

  html,body,div,span,dl,dt,dd,h1,h2,h3,h4,h5,h6,p,form,fieldset,legend,input,select,
  textarea,table,col,colgroup,thead,tfoot,tbody,th,td,button {
    margin: 0;
    padding: 0;
    color: ${({ theme }) => theme.color.dark};
    font-family: "Pretendard Variable";
    border: 0;
  }

  body {
    background: #fff;
    color: ${({ theme }) => theme.color.dark};
    font-size: 16px;
    font-weight: 400;
    line-height: 1;
    overflow-x: hidden;
    overflow-y: overlay;
  }

  ul,ol,li {
    margin: 0;
    padding: 0;
    list-style: none;
  }

  em,address {
    font-style: normal;
  }

  img {
    font-size: 0;
    line-height: 0;
    border: 0 none;
  }

  caption {
    overflow: hidden;
    width: 0;
    height: 0;
    font-size: 0;
    line-height: 0;
  }

  table {
    border-spacing: 0;
    border-collapse: collapse;
  }

  th,td {
    text-align: left;
    vertical-align: middle;
    padding: 10px 20px;
    word-break: keep-all;
  }

  a {
    text-decoration: none;
    color: ${({ theme }) => theme.color.dark};
    line-height: 1;
    cursor: pointer;
  }

  a:hover,a:focus,a:active {
    text-decoration: none;
  }

  a:visited {
    color: ${({ theme }) => theme.color.dark};
  }

  button {
    border: 0 none;
    background-color: transparent;
    cursor: pointer;
  }

  /* IE - input close button delete */
  input::-ms-clear {
    display: none;
  }

  /* RESET - chrome input background sky set */
  input:-webkit-autofill {
    -webkit-box-shadow: 0 0 0 0;
    box-shadow: 0 0 0 0;
    -webkit-text-fill-color: #848484;
  }

  input:-webkit-autofill,
  input:-webkit-autofill:hover,
  input:-webkit-autofill:focus,
  input:-webkit-autofill:active {
    transition: background-color 5000s ease-in-out 0s;
  }

  p,span,h1,h2,h3,h4,h5,h6 {
    word-break: keep-all;
  }

  b {
    font-weight: 600 !important;
  }

  textarea {
    resize: none;
  }
`;
// globalStyles.ts
import { createGlobalStyle } from "styled-components";
import reset from "styled-reset";
import { customReset } from "./customReset";

const GlobalStyle = createGlobalStyle`
    ${reset};
    ${customReset};
`;

export default GlobalStyle;
// theme.ts
import { DefaultTheme } from "styled-components";

const color = {
  main: "#14DBBA,
  gray: "#5D5D5D",
  dark: "#242424",
};

const layout = {
  contentWidth: "1280px",
};

export const theme: DefaultTheme = { color, layout };
// mixin.ts
import { css } from "styled-components";

export const scroll = css`
  ::-webkit-scrollbar {
    width: 5px;
    height: 5px;
    -webkit-overflow-scrolling: auto;
  }
  ::-webkit-scrollbar-track {
    background-color: transparent;
  }
  ::-webkit-scrollbar-thumb {
    border-radius: 4px;
    background-color: #e0e0e0;
  }
  ::-webkit-scrollbar-button {
    width: 0;
    height: 0;
  }
`;

export const textEllipsis = css`
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
`;

 

3) styled-components 설정하기

컴포넌트 내에서 전반적으로 globalstyle을 적용하고, theme을 사용할 수 있도록 처리합니다.

// _app.tsx
import type { AppProps } from 'next/app';
import { ThemeProvider } from 'styled-components';
import Layout from '../components/common/Layout';
import GlobalStyle from '../styles/globalStyles';
import { theme } from '../styles/theme';

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

 

 

theme, globalStyles 등 styled-components가 잘 적용된 화면

 

 


https://github.com/vercel/next.js/tree/canary/examples/with-styled-components

https://codesandbox.io/s/github/vercel/next.js/tree/canary/examples/with-styled-components?file=/pages/_document.tsx:164-700 

반응형