Nextjs로 프로덕션 배포하기

크로코 (Croco)·2021년 1월 13일
18

myWallets

목록 보기
2/4
post-thumbnail

안녕하세요! 👋 “myWallets”이라는 프로젝트에서 프론트엔드와 디자인, 그리고 백엔드(살짝)를 담당하고 있는 도다입니다. (업무 비중은 프론트엔드 > 디자인 >>> 백엔드 정도)

이전에 백엔드 전반을 담당하고 계시는 형주님께서 서비스 구조나 협업 방식에 대한 글을 써주셨으니, 앞 글도 봐주세요! 🔗 velog

읽으시기 전에! 😓

이 글은 회고록...처럼 의식의 흐름대로 적어나간 글입니다.
맞춤법, 어투 등이 불편하시더라도 양해해 주시기 바랍니다... 🙏

어떤 서비스인가요? 🧐

저희가 제작하고 있는 “마이월렛"은 아이폰이나 iPod touch에 존재하는 Wallet 앱에 Pass라고 불리는 카드를 간단하게 추가할 수 있도록 도와주는 사이트에요. PassKit에 관심이 있으신 분은 Apple Developer - Wallet을 참고해보세요.

어떤 기술들을 썼나요? 🍱

💪 여기서 언급하는 기술들은 모두 프론트엔드만 해당해요!

구성

주요 기술들은 다음과 같아요 🤩

  • Next.js (React)
  • Emotion (@emotion/react, @emotion/styled)
  • TypeScript

이번 프로젝트는, Next.js 10이 출시되고 얼마 되지 않아 시작한 프로젝트에요. 그래서 React 17을 도입하고, next/image 컴포넌트를 사용한 첫 프로젝트랍니다.

상태 관리는 모두 React의 기본 Context로 하고 있어요. 상태 관리 라이브러리를 사용하지 않은 이유는 Hook의 도입으로 Context를 직관적으로 관리할 수 있는 이유도 있었고, 관리해야 할 변수들이 많지 않았기 때문이에요.

배포 & CI

Production, Preview(beta) 모두 Vercel을 사용하고 있어요.

Vercel의 현재 가격 정책은 개인 사용자에게만 무료이지만, 가격 정책이 바뀌기 전의 Team들에게는 이전 가격 정책을 유지해주고 있어요! 덕분에 팀에서도 편하게 개발하고 있어요 🙇

또한 프로덕션에서도 Vercel을 유지하는 이유 중의 하나는, ICN(서울) 리전의 존재이기도 해요. 국내 모바일 사용자들이 타겟이니 만큼, 로딩 속도가 중요하기 때문이에요.

하지만 Vercel도 트래픽 제한이 존재하기 때문에, 혹여나 발생하는 트래픽 초과 상황을 방지하기 위해 모니터링 중이랍니다.

디자인 🛤


디자인에서는 Figma 툴을 이용하여 기초 베이스를 잡았어요.

Figma를 계속 사용하고 선호하는 이유라면, 커뮤니티가 커서 여러가지 목업이나 디자인 리소스를 얻을 수 있고, 다른 팀원들과 협업이 쉽고 간편하게 이루어진다는 점에서 좋다고 생각하여 사용하고 있어요.


기본적인 레이아웃은 모두 피그마에서 구상하고, 디자인으로 옮기고 있답니다. 하지만 개발하는 시간을 위하여 디자인 작업물의 디테일까지는 신경 쓰지 않아요. (어짜피 제가 만든다구요… 😅)

성능 ⏳

지난번 Croco에서 진행했을 때의 프로젝트는 성능을 전혀 신경 쓰지 않고 구현했었어요.

이번 프로젝트에서도 성능보다는 구현을 우선으로 하기로 했어요. 모든 기능을 모두 제작한 후에 성능에 대한 최적화나 리팩토링을 진행하기로 하여서, 기본적인 기능 구현이 끝난 현재도 계속 성능 최적화를 진행하고 있답니다.

추가적으로, React 렌더링 최적화에 대한 이해를 하기 쉬웠던 글을 남길게요. React 렌더링 이해 및 최적화 (With Hook)

Memoization

함수 재사용의 중요성을 알게 된 이후로는 useCallback(혹은 useMemo)을 사용하고 있어요.

하지만 useCallback을 항상 붙이는 것은 아니에요! Your Guide to React.useCallback()이라는 글을 참고하여 중요하거나 반복 사용되는 함수, 계산이 많은 함수들에게만 useCallback을 사용하고 있어요.

또한 React.memo() 를 사용하는 경우도 종종 있는데요. 주로 props가 없거나 거의 변경되지 않는 레이아웃에만 사용하고 있어요. (Use React.memo() wisely 글을 읽어보고 도움을 받았답니다)

불필요한 리렌더링

Virtual DOM 개념을 이용하는 React에게는 리렌더링이라는 과정이 존재해요. 당연하겠지만 불필요한 렌더링이 일어나는 경우엔 성능상으로 손해에요. 그래서 불필요한 리렌더링을 방지하기 위해 약간 삽질을 했답니다.

🔼 찾아보니 React Devtools 설정에서 저 체크박스를 치면 렌더링마다 하이라이팅이 된다하여 켰지만, 직관적으로 보이지 않더라구요. (제가 못쓰는 것일지도)

🔼 그래서 Elements를 직접 보며, 어느 부분이 변경되는지 확인하면서 리렌더링을 확인했어요. (지금 확인해보니, React Devtools에 있는 Components에서도 확인할 수 있네요)

그리고 또 다른 방법은, useEffect를 이용하는 방법이에요.

useEffect(() => {
  console.log('Render: Header');
}, []);

이렇게 콘솔에 로그를 찍으면서… 라우팅이 변경될 때 렌더링이 이루어지는지 확인하는 방법도 사용했어요!

다크 모드 🎨

이 문단을 작성하는 지금 시각, 오전 3시 1분.

낮보단 밤이 더 좋은 평범한 개발자인 저는 가끔씩 방 불을 끄고 컴퓨터를 할 때가 있어요. 밝은 화면은 눈이 아파서 저는 다크 모드를 좋아해요. 디자인 초반부터 다크 모드를 기획했었답니다.

구현... (꼼수)

💬 참고! 이 문단은 다크 모드의 기술적 구현에 대해서만 다뤄요!

라이트 모드(?)를 모두 스타일링 후에 서비스 도중에 다크 모드를 추가하는 작업이였기에 효율적인 방법을 선택하려고 했어요.

구현 초반에는 각 요소에 CSS properties를 지정하는 방법도 생각했었지만, 생각보다 광범위하여 금방 포기하게 됬어요.

그래서 선택한 방법은 Context(전역 상태) + CSS!

useEffect(() => {
  if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
    setDark(true);
  }
}, []);

🔼 ContextProvider 컴포넌트가 로딩 될 때에 다크 모드인지 감지합니다

<LayoutContext.Provider value={{ dark, setDark, changeDark }}>
  <div className={dark ? 'theme-dark' : ''}>{children}</div>
</LayoutContext.Provider>

🔼 ContextProvider가 전역을 감싸는 div를 만듭니다

const styled = styled.div`
  .theme-dark & {
    color: #7d86a0;
  }
`

🔼 그 후, 요소를 구성하는 styled-components에서 .theme-dark를 부모 객체로 받아서 스타일링 하는 꼼수(?)를 쓰고 있답니다.

이 방법을 고안한 이유는... 귀찮아서에요. 스타일링마다 Context 받기도 귀찮고, CSS Properties를 만들기도 귀찮았기에 스타일링이 조금 늘어나더라도 이런 방법을 선택했어요. 😅

결과적으론 리렌더링의 부담도 없고 예상처럼 잘 작동해주는 것 같아 고맙네요.

🥲 단점 지금은 다크 모드 감지가 유동적이지 않아요. macOS나 다른 운영체제에서 제공하는 자동 색상 모드를 지원하지 못한다는 점.

코드 구현에 정답은 없다고 생각하기 때문에, 다른 방법이 있으면 제안해주셔도 좋아요 🙇

구조 🏗

유지보수가 힘든 코드를 관리하는 것은 어렵죠. 저도 제가 짠 코드들에 많이 당해본 기억이 있는데요.

이번 프로젝트는 프론트엔드에서 아키텍처를 처음으로 적용해 본 프로젝트에요. 지금 껏 백엔드에서는 여러가지 아키텍처를 많이 이용했었지만, 프론트엔드에서는 해봤자 Atomic Design 정도만 사용해봤었거든요.

팀 리더의 소개로, MVVM(Model-View-ViewModel) 패턴을 도입했어요. MVVM 패턴이 궁금하신 분들은 이 글을 참고하세요!

첨부한 글과는 저희 프로젝트는 다른 점이 조금 있어요!
글에서는 class를 사용하고 있지만, 저희 프로젝트에는 Hooks처럼 도입했어요.

// Model
const { passes, loading } = useMembershipPass(id);

그 이외에도 여러 다른 점들이 존재하지만... 아직 저도 적응 단계라서 설명을 못하겠네요. 😓

마치며 🙇

설마 여기까지 봐주신건가요...?🤭 쓰다보니 글이 너무 길어졌지만, 부족한 글을 읽어주셔서 감사합니다.

마이월렛은 지금도 계속 개선 중인 프로젝트에요. 구현 예정인 기능들과, 기획 중인 기능들도 있어요. 앞으로 계획이 어떻게 될지는 모르겠지만, 힘내볼 예정입니다 😋

이 글 뒤로도 팀에 유일한 실무 경력 개발진이자 저희 팀 리더 분께서 개발기를 적어주실 예정이니, 관심 가져주시면 감사하겠습니다 😎

팀 계정이라 댓글 확인이 힘들어요. 궁금하신 점이 있으시다면 me@doda.dev로 메일 부탁드릴게요 🧑‍🔧

  • (광고) Apple Wallet에 관심 있으신 분들은 마이월렛 써주세요…
profile
세상을 바꾸는 사이드 프로젝트 팀, 크로코입니다.

2개의 댓글

comment-user-thumbnail
2021년 1월 14일

사용중인 서비스의 구조를 보게되다니 신기하네요! 잘 읽었습니다!

1개의 답글