Next.js + Emotion + Tailwind + TS 구성하는 간단한 방법 소개

프로젝트 소개

이번 프로젝트는 Next 프레임워크에 Typescriptemotion + twin.macro를 함께 구성하는 방법에 대한 과정을 소개한다. twin.macro 공식 깃허브1 가보면 Next.js + Emotion + Tailwind + JS(자바스크립트) 환경 구성 방식2에 대해 설명이 아주 잘 되어 있긴 해서 이를 참고하면 좋을 것 같다.

Typescript로 설정한 환경 설정 방식도 Javascript환경 설정 방식과 유사하게 문서화 되어 있을 줄 알았는데 degit 을 활용해 클론하여 세팅할 수 있게 가이드 해준다.

degit은 뭐지?: 일반적으로 Git을 사용하여 저장소를 복제(clone)하면 저장소의 전체 파일 및 디렉토리를 가져온다. 하지만 degit을 사용하면 필요한 디렉토리와 파일만 선택적으로 가져올 수 있다. 패키지의 일부분만 필요한 경우에도 전체 패키지를 복제하고 설치할 필요 없이, degit을 사용하여 필요한 파일만 가져올 수 있다.

프로젝트 세팅할 땐 편해서 더 좋은데 어떻게 세팅을 구성했는 지 궁금하긴 하다. 참고로 클론한 프로젝트 세팅은 next.js /app 디렉토리를 사용하는 버전이긴 하다. 2~3달 전만 해도 /page 디렉토리 폴더 세팅으로 클론 받았던 것 같은데 바뀌었다. /app 디렉토리 한번 써보기도 할겸 이걸로 세팅해서 토이 프로젝트 진행해봐도 좋을 듯 하다.

프로젝트 세팅 방법

npx degit https://github.com/ben-rogerson/twin.examples/next-emotion-typescript folder-name

폴더 이름 잘 설정해서 클론하고 나서 입맛에 맞게 세팅을 진행하면 된다. 이렇게 하면 세팅이 끝이긴 한데.. 이슈가 발생했다.

이슈1. tw 작성 방식으로 작성시 오류가 발생한다.

<div tw="text-red-100">텍스트</div>

해결1. withTwin.js 설정 파일 수정

babel-loaderpresets 옵션 추가가 필요하다고 한다. @emotion/react으로 JSX의 생성 함수를 Emotion 라이브러리의 함수로 대체한다. Emotion 스타일링 방식을 JSX 와 결합할 수 있게 해준다.

const path = require("path");

// The folders containing files importing twin.macro
const includedDirs = [path.resolve(__dirname, "src")];

module.exports = function withTwin(nextConfig) {
  return {
    ...nextConfig,
    webpack(config, options) {
      const { dev, isServer } = options;
      // Make the loader work with the new app directory
      const patchedDefaultLoaders = options.defaultLoaders.babel;
      patchedDefaultLoaders.options.hasServerComponents = false;
      patchedDefaultLoaders.options.hasReactRefresh = false;

      config.module = config.module || {};
      config.module.rules = config.module.rules || [];
      config.module.rules.push({
        test: /\.(tsx|ts)$/,
        include: includedDirs,
        use: [
          patchedDefaultLoaders,
          {
            loader: "babel-loader",
            options: {
              sourceMaps: dev,
              presets: [
                [
                  "@babel/preset-react",
                  { runtime: "automatic", importSource: "@emotion/react" },
                ],
              ],
              plugins: [
                require.resolve("babel-plugin-macros"),
                require.resolve("@emotion/babel-plugin"),
                [
                  require.resolve("@babel/plugin-syntax-typescript"),
                  { isTSX: true },
                ],
              ],
            },
          },
        ],
      });

      if (!isServer) {
        config.resolve.fallback = {
          ...(config.resolve.fallback || {}),
          fs: false,
          module: false,
          path: false,
          os: false,
          crypto: false,
        };
      }

      if (typeof nextConfig.webpack === "function") {
        return nextConfig.webpack(config, options);
      } else {
        return config;
      }
    },
  };
};

해결2. 각 페이지 컴포넌트 .tsx 파일 상단에 주석 추가

파일별로 JSX의 생성 함수를 어떤 소스에서 가져올지를 지정하는 방법이다. 이 주석을 상단에 추가하면 해당 파일의 JSX는 @emotion/react에서 생성 함수를 가져와 처리하겠다는 뜻.

/** @jsxImportSource @emotion/react */

// 페이지 컴포넌트
export default function Page() {
  return <>페이지 컴포넌트</>;
}

이슈2. /pages router 로 변경할 수 있을까?

  • /pages 폴더 추가
    • _app.tsx
      import GlobalStyles from "@/styles/GlobalStyles";
      import type { AppProps } from "next/app";
      
      const App = ({ Component, pageProps }: AppProps) => {
        return (
          <>
            <GlobalStyles />
            <Component {...pageProps} />
          </>
        );
      };
      
      export default App;
      
    • _document.tsx
      import React from "react";
      import { Head, Html, Main, NextScript } from "next/document";
      
      const Document = () => {
        return (
          <Html lang="en">
            <Head />
            <body>
              <Main />
              <NextScript />
            </body>
          </Html>
        );
      };
      
      export default Document;
      
    • index.tsx
      import tw from "twin.macro";
      
      const App = () => (
        <div>
          <p>메인페이지입니다.</p>
        </div>
      );
      
      export default App;
      

위와 같이 세팅하고나면 /pages 디렉토리 세팅 방식대로 페이지 컴포넌트를 추가하면 된다!

정리

next.js + ts + emotion + twin.macro의 조합은 실무에서도 채택한 방향인 만큼 한동안은 꾸준하게 사용할 것 같다. 개인 사이드 프로젝트 진행할 때에도 사용하려고 한다.

프로젝트 세팅은 매번 할 때 마다 새로운 이슈들을 만나게 된다. 매번 라이브러리, 프레임워크 버전도 꾸준히 바뀌어서 매번 업데이트 내용을 주의깊게 살펴봐야 할 것 같다.

완전 제로에서 위의 구성으로 세팅하지 않아도 보일러플레이트를 제공해준 twin.macro 깃 너무 감사하다!

추가로 어떻게 환경설정 구성되었는지도 뜯어봐야겠다.

다음엔?

프레임워크 환경 설정도 해봤으니 백지상태에서 프로젝트 세팅하는 방법도 해봐야겠네? 라는 생각이 들었다. 마침 flab 프로젝트 실습도 프레임워크없는 프론트엔드 개발을 주제로 실습중이니 환경설정을 해보며 삽질해본 과정을 정리해보는 것도 재밌겠다.