blog logo
Published on

5월 사이드 프로젝트를 하며 만났던 에러 정리

들어가며

현재 나는 디자이너 한 분, 백엔드 한 분, 프론트엔드 한 분과 함께 숏폼 영상 컨텐츠를 올리고 영상을 자랑하는 SNS 프로젝트를 진행 중에 있다. 이 프로젝트는 넘블 챌린지를 참가하게 되면서 진행하게 된 프로젝트이다. 프로젝트 후기는 따로 올릴 예정이지만, 프로젝트를 하면서 중간 중간에 만났던 에러들을 나중에 기록할 때 까먹을까봐 블로그를 열었다ㅜㅜ


Next.js에 styled-components 적용했을 때 만난 에러

가장 먼저 아무 설정도 해주지 않고 Next.js에서 styled-components를 사용하면 다음과 같은 에러를 만날 수 있다.

Next.js에서 styled-components 에러 발생한 이미지

이 에러는 무엇이고, 발생하는 원인은 무엇일까? 바로 Next.js가 기본적으로 Pre-Render를 하고 있기 때문이다. 그리고 Pre-Render에는 다음 2가지와 같은 Stage가 있다.

  1. Initial Load Stage
  2. Hydration Stage

초기 로드(Initial Load Staga) 과정에서 정적인 HTML을 렌더링한 후, JS 파일 로드(Hydration Stage) 과정을 거치면서 Sync가 되는 렌더링 과정이다.

그러면 위의 설명과 styled-components는 어떠한 연관이 있길래 저런 에러가 났을까. 기본적으로 styled-components는 JS에 의해 동적으로 CSS를 생성하는 CSS-in-JS 방식이다. 그렇다보니 SSR(SSG) 방식으로 HTML을 렌더링 한 뒤에 내부 라우팅을 통해 CSR을 하는데(동적으로 CSS를 생성하면서), 이 과정에서 서로 생성하는 className 해시값이 달라서 생긴 에러 메시지이다.

그렇다면 이 에러는 어떻게 해결해 줄 수 있는가? 바벨 플러그인 하나만 설치해주면 간단하게 에러 메시지를 없애줄 수 있다.


npm i -D babel-plugin-styled-components
// .babelrc 파일 생성 후

{
  "presets": ["next/babel"],
  "plugins": [
    [
      "styled-components",
      {
        "ssr": true,
        "preprocess": false,
        "fileName": true,
        "displayName": true,
        "pure": true
      }
    ]
  ]
}

사실 Next.js + styled-components 조합으로 프로젝트를 해 본 사람이라면 여기까지는 다 알만한 내용들이었다. 하지만 내가 만난 에러는 이 부분과 다른 내용이다.

먼저 서버사이드에서도 styled-components가 깨지지 않고 잘 돌아가게 하기 위해 _document.tsx파일에 styled-components 모듈에서 제공하는 ServerStyleSheet 클래스를 활용했다. 이 코드는 여기를 참고했다.

이후 컴파일 단계에서 에러가 발생했는데, 다음과 같은 에러가 발생했다.

_document.tsx파일 styled-components 적용 에러

컴파일 단계에서 에러를 마주쳤기 때문에 타입에러라는 것은 확신했다. 하지만 도무지 어떻게 해결해야 하는지 감이 오질 않았고, 구글 형님들의 도움을 받고 싶었으나 관련 글들이 없었다. 그래서 현재 프로젝트는 같이 하고 있지 않지만 같이 FE 스터디를 하고 있는 분들에게 도움을 청했고, 다행히 에러를 해결할 수 있었다.

먼저 위의 에러 발생 이유를 보면 styles의 JSX.Elment가 ReactElement<any, string | JSXElementConstructor<any>>[] | ReactFragment | undefined; }와 호환이 되지 않는다고 나와있다.

그리고 코드를 보면 try/catch문에서 try의 styles 반환값이 다음과 같다.

styles: <>
  {initialProps.styles}
  {sheet.getStyleElement()}
</>;

위의 코드 반환 값은 JSX.Element이고 이 코드가 ReactElement<any, string | JSXElementConstructor<any>>[] | ReactFragment | undefined; }의 타입과 호환이 되지 않는 모양이다. 원인을 찾은 후 해결방법은 간단했다.

styles: [
  <>
    {initialProps.styles}
    {sheet.getStyleElement()}
  </>,
];

styles의 반환값에 대한 타입을 JSXElementConstructor<any>>[]로 맞춰주었다. 이렇게 해주었더니 _docment.tsx 파일에서 아무 에러가 나지 않는 것을 확인할 수 있었다! 👏👏👏




cannot find module "파일명" or its corresponding type declarations

프로젝트가 어느덧 끝나갈 무렵, 다 같이 모여서 실서버 배포를 하기로 했다. build를 실행해보고 문제없는 것 까지 확인 후 main 브랜치에 모든 코드를 머지했다. 이후 백엔드 분께서 프론트엔드 배포도 같이 진행해 주셨다. 화면 공유를 하고 같이 진행을 하는데, pm2 무중단 배포를 위해 배포 과정에서 빌드를 하는데 cannot find module "@components/Common/파일명" or its corresponding type declarations 에러가 발생했다. 어라? 분명 로컬에서 아무리 빌드를 해봐도 저런 에러가 발생하지 않았는데 도대체 무슨 에러인가..
(TMI: 이 날 이 문제 때문에 아침 8시까지 잠을 못잤었다..😵‍💫)

사실 난 이 에러를 이번에 처음 만난 것이 아니다. 예전에 Next.js 처음 공부했을 때 간단하게 블로그를 만들고 vercel을 이용해서 배포한 적이 있었는데, 그때도 저 에러를 만난 적이 있었다. 하지만 관련 문서를 찾고 적용해 봐도 해결이 되지 않았었다. 분명 tsconfig 설정값으로 파일들의 경로를 설정해 주었는데, 배포 시점에서 파일들의 경로를 찾지 못하고 있는 문제는 감각적으로 느끼고 있었다.. 하지만 느낌만 알고 해결법을 찾지 못하고 있었다. 그러다가 파일명들을 모두 소문자로 바꿨더니 해결이 된 적이 있었다. 결국 이유를 찾지도 못한 채...

하지만 이렇게 또 만날 줄이야.. 이번에는 정말 원인과 해결법을 찾고 싶었다. 너무 원초적이지만 일단 팀원들과 파일명을 모두 소문자로 바꿔서 배포가 되는지부터 확인해 보자는 의견이 나왔고, 시도해 보았다. 그리고.. 이게 무슨일인가... 배포가 되고야 말았다.

그러면 도대체 무슨 원인인지 찾고 싶었다. 그러다가 우연히 git은 대소문자를 구분하지 않는다.라는 글을 보게 되었고, 바로 git 레포지토리를 확인하러 갔다.

우리는 components 폴더안에 Common이라는 폴더에서 공통 컴포넌트들을 관리하고 있다. 하지만 git 레포지토리에는 common이라는 소문자 폴더로 만들어져있었다. 곰곰이 생각해보니 처음에 common으로 git에 push를 한 적이 있었고, 이후에 팀원들과 Common으로 폴더명을 바꾸자고 해서 바꾼적이 있었는데, 중간에 바꾼 폴더명은 git이 인식을 하지 못한 것이다. 왜냐? 대소문자를 구분하지 않으니까....ㅠㅠ

이 문제는 배포를 할 때 비로소 알게 되었고, 문제의 원인은 몇 시간만에 발견하게 되었고, 해결법을 찾는데는 오랜 시간이 걸리지 않았다. 하하.. 그리고 해결 방법은 어렵지 않았는데 바로 git이 대소문자를 구분하게 만들어주면 되는 것이었다.

// 대소문자 구분함
git config core.ignorecase false

// 대소문자 구분 안함
git config core.ignorecase true

git config core.ignorecase false을 하게 되면 갑자기 엄청난 Changes 파일이 뜬다!!! Common안에 있던 파일들이 많다보니 commit 변경점 개수가 엄청 많아졌다...

its corresponding type declarations 에러 해결 후 커밋 이미지

PR을 확인해보니 32개의 파일이 다시 생성되었다...

its corresponding type declarations 에러 해결 후 레포지토리 이미지

그리고 다시 git 레포지토리를 확인해보니 기존 소문자 폴더 common은 남아있고 새로 Common이 생성된 것을 볼 수가 있다. 그리고 저 부분은 github에서 삭제해주었다. 문제를 해결하고 실서버 배포까지 마무리가 되어서 정말 행복하게 아침8시에 잠을 자러 갈 수 있었다.




metarial UI CSS

mui를 쓰면서 css 부분에서 애를 먹은 경험이 있다. 회사에서 antd를 사용하는 프로젝트에 참여한 적이 있지만, mui는 이번 프로젝트에서 처음 도입해 보았다.

mui css 오류 영상

위의 gif를 보면 관련영상 <-> 댓글을 왔다 갔다 하면 비디오 상세 내용들(제목, 설명글 등)의 CSS가 깨지는 것을 볼 수가 있다. 지금 제목을 mui CSS라고 적어서 그렇지만, 이 이유를 찾는데도 적지 않은 시간을 쏟았다. 개발자 도구를 열고 태그들의 css 적용값들을 하나씩 바꿔가며 원인을 찾고 있었다. css 대부분 margin과 padding을 최소화하고, flex box 비율값 그리고 flex-gap만으로 간격들을 조정하려고 했기 때문에 이 부분에 문제가 있다고 생각했다. 하지만 아무리 봐도 아닌 것 같고, rem이 문제인가? 라고도 생각했지만 개발자도구의 html 태그들의 변경점 부분을 잘 보니 찾을 수 있었다.

mui css 오류 영상

댓글쪽으로 갔을 때 body태그 자식으로 div 태그 하나가 생성되는걸 볼 수 있다. 저 태그의 class 이름을 보니 mui로 가져온 무언가가 문제라는 것을 인식했고, mui 컴포넌트를 유심히 살펴 보았다. 그리고 mui의 Drawer 컴포넌트를 가져오면서 이상한 태그하나가 같이 딸려온 것을 확인할 수 있었다.

mui css 오류 영상

저녀석이었구나.. 나를 힘들게 한게.. 저 <CSSBaseline />태그로 인해 mui를 불러온 부분만 mui CSS가 적용이 된 것이었다. 대댓글 리스트창을 열기 위해 mui Drawer 컴포넌트를 사용했고, Drawer 컴포넌트가 댓글 리스트 자식 컴포넌트로 있다보니, 연관되어 있는 모든 컴포넌트들이 mui CSS가 적용된 것이다. 저 한 줄을 없애주니 바로 CSS문제가 해결이 되었다.

mui css 오류 해결 영상

그리고 대댓글창을 열기 위해 사용한 Drawer 컴포넌트도 잘 되는 것까지 확인하고 마무리를 지었다.




Duplicate atom key

react-query를 사용하다보니 정말 클라이언트 상태값을 사용하는 일이 거~~의 없다. 그럼에도 기능 구현상 사용해야 할 경우가 있었는데, 전역값을 우리는 recoil을 사용해서 해결했다.

그러면 recoil atom은 다음과 같이 만들 것이다.

import { atom } from "recoil";

export const updateVideoIdState = atom<number | null>({
  key: "updateVideoIdState",
  default: null,
});

하지만 이렇게 recoil을 사용하면 다음과 같은 에러를 필히 만날 것이다.

recoil Duplicate atom key 에러

Duplicate atom key에러는 recoil의 key값이 중복되었다는 뜻인데, Next.js의 SSR 환경에서 발생하는 에러인듯하다. 조금만 찾아보니 이 에러 이슈는 공식 이슈로 아직까지도 open되어 있는 상태이다.
👉 공식 이슈 보러가기

그래서 나는 여기 나와있는대로 우회 방법을 선택해서 해결했다. 설정값은 다음과 같다.

npm i -D next-intercept-stdout
// next.config.js
/** @type {import('next').NextConfig} */
const withInterceptStdout = require("next-intercept-stdout");

const nextConfig = {
  reactStrictMode: false,
};

module.exports = withInterceptStdout(nextConfig, (text) =>
  text.includes("Duplicate atom key") ? "" : text,
);

이렇게 적용하고 나서 실행해 보았더니 더이상 해당 에러를 터미널에서 만나지 않았다!!! 공식 이슈를 찾고 나서 손 쉽게 해결됐다.

recoil Duplicate atom key 에러 해결




글을 마치며

아직 프로젝트가 진행 중이지만, 그동안 만났던 에러들을 정리해 보았다. 이전에 만났던 에러들을 정리하지 않았더니 또 같은 에러를 만났을 때 똑같은 검색어로 구글링을 하는 나의 모습을 보고 현타가 왔다. 한 번 만났던 에러는 더이상 구글링하고 싶지 않은 마음에 블로그 글을 작성했다.

이번 프로젝트를 하면서 많은 에러를 만나지 않았지만 에러를 만날 때 마다 기능 구현에 대한 내용, 기술 스택에 대한 내용의 블로그 글도 좋지만 에러 원인과 해결법에 대한 문서는 정말 정말 귀하고, 중요하다는 것을 다시 한 번 깨닫게 되는 계기가 됐다.

특히 에러에 대한 블로그 글은 쓰기가 쉽지 않다. 왜냐면 에러가 발생하자마자 캡쳐 후 아싸 블로그 각! 이러는 경우는 드물기 때문이다. 선 당황 후 어떻게든 해결하기 바쁘기 때문이다ㅠㅠ 하지만!! 그럼에도 에러가 발생하면 블로그 각을 잡는 연습을 할 필요가 있다.

이 사이드 프로젝트가 끝나고 다른 사이드 프로젝트인 YAPP 웹뷰 사이드 프로젝트를 진행할 것 같다. YAPP 사이드 프로젝트가 기획이 거의 다 끝나가기 때문에 이어서 바로 또 개발을 할 수 있을 것 같다. 이 프로젝트는 8월까지 진행되는데, 짧지 않은 시간동안 에러 내용을 다룬 블로그 각을 잡는 연습을 해봐야겠다.