코드잇 스프린트 마지막 프로젝트, 티마고치 종료 회고

길었던 코드잇 스프린트의 마지막 여정, 티마고치 프로젝트가 한 달 반의 대장정을 마치고 드디어 마무리되었습니다. 이전 프로젝트들에 비해 긴 호흡으로 진행되었던 만큼, 에너지 관리와 팀원들과의 협업이 더욱 중요하게 느껴졌습니다. 특히 다섯 명의 팀원이 하나의 목표를 향해 나아가는 과정은 쉽지 않았지만, 그만큼 값진 경험을 선사했습니다.

프로젝트 화면: 티마고치

프로젝트 화면

티마고치는 이전 프로젝트였던 Todo-Todo와 유사한 결의 서비스로, 팀원들과 함께 해야 할 일을 공유하고 체크할 수 있는 확장된 형태의 투두 리스트입니다. 자유 게시판을 통해 사용자 간 소통이 가능하며, 반복 일정 추가 및 공유 기능까지 제공합니다. Drag and Drop (DND)이나 반복 기능 등 구현 난이도가 높은 편이었고, 이번 기수에 새롭게 기획된 프로젝트라는 점이 매력적으로 다가와 최종적으로 선택하게 되었습니다.

기술 스택 및 선택 이유

  • Next Page Router: SSG와 SSR을 모두 지원하며, App Router에 비해 안정성이 높다고 판단하여 선택했습니다.
  • TypeScript: 컴파일 단계에서 타입 에러를 사전에 방지하여 프로젝트의 안정성을 향상시키기 위해 사용했습니다.
  • Tailwind CSS: 클래스 이름 충돌 가능성이 없고, 미디어 쿼리 적용이 용이하여 반응형 웹사이트를 효율적으로 개발할 수 있었습니다.
  • Tanstack-query: 서버 데이터를 효율적으로 캐싱하여 응답 속도를 개선하고, 네트워크 요청 시 로딩 및 에러 처리 과정을 간편하게 구현할 수 있었습니다.
  • Zustand: 전역 상태 관리를 위해 사용했으며, 다른 라이브러리에 비해 학습 곡선이 낮고 보일러플레이트 코드가 적다는 장점이 있었습니다.
  • Git/Jira: 각 기능별 브랜치의 커밋 내역을 깔끔하게 관리하기 위해 스쿼시 머지를 활용했습니다. Jira에서 이슈를 생성하고 브랜치를 연결하여 GitHub PR을 통해 효율적인 협업 워크플로우를 구축했습니다.

기획 기간 (7/25 ~ 7/30)

비교적 여유로운 기획 기간 동안 팀원들과 함께 서비스의 User Flow를 상세하게 정의하고, API 명세서 및 요구사항을 꼼꼼히 검토했습니다. 또한, 프로젝트 전반에 걸쳐 적용될 컨벤션을 설정하고 UI 모델링 작업을 진행했습니다. 기획 단계에서는 실시간 협업이 용이한 Tldraw 툴을 활용하여 모든 팀원이 함께 아이디어를 공유하고 발전시키는 데 집중했습니다.

  • User Flow: Figma 시안을 기반으로 사용자 흐름을 시각적으로 표현하여 프로젝트의 전체적인 그림을 명확히 했습니다. 복잡한 다이어그램 대신 직관적인 시안 중심의 접근 방식을 택했습니다.

  • 요구사항 체크: 제공된 요구사항을 팀원들과 심층적으로 논의하며 누락된 부분이나 구체화해야 할 사항들을 추가적으로 정의했습니다.

  • UI 모델링: 컴포넌트 단위로 UI를 분리하고 각 컴포넌트에 명확한 네이밍 규칙을 적용하여 개발 과정에서의 혼선을 줄이고 일관성을 확보했습니다.

  • API 명세서 체크: Swagger를 통해 제공된 API 엔드포인트, 요청/응답 데이터 구조 등을 상세히 파악하여 개발 방향을 설정했습니다.

  • 기술 스택: 프로젝트에 적용할 기술 스택을 비교 분석하고, 각 기술을 선택한 이유를 명확하게 정리하여 팀원 간의 이해도를 높였습니다.

  • 컨벤션 및 폴더 구조: 프로젝트 시작 전에 폴더 구조와 코딩 컨벤션을 명확하게 정의하여 개발의 효율성과 유지보수성을 향상시키고자 노력했습니다.

  • 작업 단위: 구현해야 할 기능들을 작은 단위로 분리하고, 각 팀원의 역량과 선호도를 고려하여 효율적으로 작업을 분배했습니다.


구현 기간 (7/31 ~ 8/20)

총 3주에 걸친 구현 기간 동안, 우리는 주 단위로 목표를 설정하고 개발에 매진했습니다. 1차 구현에서는 공통 컴포넌트나 사용자 인증 기능과 같이 프로젝트의 기반이 되는 기능들을 우선적으로 개발했습니다. 2차 및 3차 구현을 통해 나머지 기능들을 순차적으로 완성해 나갔습니다.

Jira를 적극적으로 활용하여 각 이슈에 대한 브랜치를 생성하고, 로컬 환경에서 작업한 내용을 해당 브랜치에 커밋하는 방식으로 개발을 진행했습니다. Jira를 체계적으로 사용한 것은 이번 프로젝트가 처음이었는데, 브랜치 충돌 위험을 줄이고 프로젝트 진행 상황을 시각적으로 파악하는 데 매우 유용했습니다. 다만, 간단한 작업의 경우에도 Jira를 거쳐야 하는 번거로움 때문에 때로는 불필요하게 브랜치를 생성하게 되는 경우도 있었습니다.

맡았던 역할

유저 기능

회원가입, 로그인, 로그아웃, 탈퇴 등 사용자의 인증 및 인가 관련 기능을 구현했습니다. 네트워크 요청 시에는 Tanstack-query를 활용하여 데이터 fetching 및 캐싱을 처리하고, onSuccess와 onError 콜백 함수를 통해 Toast 알림으로 사용자에게 작업 성공 여부를 즉각적으로 알렸습니다.

프로젝트 초기부터 관심사 분리를 중요하게 생각하여, UI 컴포넌트, 폼 데이터 상태 관리 Hook, 그리고 핸들러 로직을 담은 Hook을 각각 분리하여 구현했습니다. 또한, 유효성 검사를 효율적으로 처리하기 위해 Zod 라이브러리를 적극적으로 활용했습니다.

로그인 유지 기능을 위해 처음에는 사용자 정보를 로컬 스토리지에 저장했으나, 보안상의 우려로 멘토링 시간에 논의한 결과, 사이트 접속 시마다 사용자 정보를 다시 요청하는 방식으로 변경했습니다. 이 과정에서 팀원들과의 의견 충돌과 결정 번복이 잦아 어려움을 겪기도 했습니다. 최종적으로는 팀 회의를 통해 쿼리와 로컬 스토리지 모두에 사용자 정보를 저장하는 절충안을 택했습니다.

middleware / axios interceptor

Next.js 공식문서를 참고하여 미들웨어를 구현했습니다. 미들웨어는 사용자의 로그인 상태를 확인하여 인증된 사용자만 특정 경로에 접근할 수 있도록 리다이렉트하거나, 로그인한 사용자가 불필요한 회원가입/로그인 페이지에 접근할 경우 팀 대시보드로 이동시키는 역할을 수행했습니다. 로그인 여부는 Refresh Token의 존재 여부를 기준으로 판단했습니다.

스프린트 프로젝트 중 처음으로 Axios를 사용하게 되면서, Axios의 장점을 최대한 활용하기 위해 인터셉터를 구현했습니다. Request 인터셉터는 모든 요청 헤더에 Access Token을 자동으로 삽입하는 역할을 수행했으며, Response 인터셉터는 401 인증 에러 발생 시 Refresh Token을 사용하여 Access Token을 재발급받고 원래의 요청을 다시 시도하는 로직을 구현했습니다. Axios 공식문서가 매우 상세하여 비교적 쉽게 인터셉터를 구현할 수 있었습니다.

현재 미들웨어와 인터셉터가 인가 관련 기능에만 집중되어 있지만, 앞으로 다른 유용한 기능들을 추가적으로 통합하여 활용도를 높이는 방안을 고민해 볼 예정입니다.

팀 정보, 유저 정보 수정

API 명세에 따라 수정 시 이름 변경은 JSON 형태로 이름 데이터만 전송하고, 이미지 수정은 별도의 API를 통해 이미지를 업로드하여 URL을 획득한 후 해당 URL을 JSON 형태로 전송하는 방식으로 구현해야 했습니다. 이를 위해 다음과 같은 점에 유의했습니다.

  • JSON 형태를 유동적으로 구성할 수 있도록 처리했습니다.
  • 이미지 파일을 URL로 변환하는 API 요청을 먼저 처리하도록 구현했습니다.

Tanstack-query를 사용했기 때문에, 이미지 파일이 존재할 경우 이미지 URL을 성공적으로 획득한 후에 정보 수정 API 요청을 보내도록 처리하고, 이미지 파일이 없는 경우에는 바로 API 요청을 전송하도록 구현했습니다. 초기에는 빈 객체를 생성하여 조건에 따라 데이터를 조합하는 방식으로 개발했습니다. 팀 정보 수정과 유저 정보 수정 기능은 유사한 UI 구조와 JSON 형태를 가지고 있어, 폼 데이터와 API 요청 로직을 처리하는 Custom Hook을 만들어 각 페이지 컴포넌트에서 재사용할 수 있도록 했습니다.

router loading

Next.js Page Router는 App Router와 달리 자체적인 Loading 컴포넌트를 제공하지 않으므로, 사용자 경험 개선을 위해 페이지 이동 시 로딩 컴포넌트를 표시하는 기능을 구현해야 했습니다. 이를 위해 _app.js 컴포넌트 내에서 useRouter 훅과 useEffect를 사용하여 라우터 이벤트를 감지하고 로딩 상태를 관리했습니다.

const [loading, setLoading] = useState(false);
const router = useRouter();

  useEffect(() => {
    const handleRouteChangeStart = () => setLoading(true);
    const handleRouteChangeComplete = () => setLoading(false);

    router.events.on("routeChangeStart", handleRouteChangeStart);
    router.events.on("routeChangeComplete", handleRouteChangeComplete);
    router.events.on("routeChangeError", handleRouteChangeComplete);

    return () => {
      router.events.off("routeChangeStart", handleRouteChangeStart);
      router.events.off("routeChangeComplete", handleRouteChangeComplete);
      router.events.off("routeChangeError", handleRouteChangeComplete);
    };
  }, []);

위 코드는 _app.js 컴포넌트 내의 useEffect 훅에서 라우터의 routeChangeStart, routeChangeComplete, routeChangeError 이벤트를 구독하고, 각 이벤트 발생 시 loading 상태를 업데이트하여 로딩 컴포넌트를 조건부로 렌더링하는 방식으로 구현되었습니다.

toast

Toast 알림 기능은 Zustand를 사용하여 전역적으로 상태를 관리하고, 사용자 편의성을 높이기 위해 Custom Hook을 만들어 원하는 메시지와 아이콘을 쉽게 사용할 수 있도록 구현했습니다. 이와 관련된 자세한 내용은 별도의 글로 정리했으므로, 여기서는 간략하게 언급합니다.


QA 기간 (8/21 ~ 8/27)

세 번의 구현 기간을 마친 후, 약 일주일 동안 QA 기간을 가졌습니다. 매일 스크럼 회의를 통해 발견된 버그들을 공유하고 수정하는 과정을 반복했습니다. 리팩토링 과정에서 새로운 버그가 발견되기도 했지만, 꾸준한 테스트를 통해 프로젝트의 완성도를 높이기 위해 노력했습니다.

그 결과, 약 60여 개의 버그 리스트 중 90% 이상을 해결할 수 있었습니다. 프로젝트 발표 당시, 버그를 자동으로 탐지하고 리포트를 생성해 주는 유용한 서비스에 대한 제안을 받았는데, 다음 프로젝트에 꼭 활용해 볼 계획입니다.


프로젝트를 끝내며…

파트 4 프로젝트는 이전 파트들에 비해 유난히 힘들었던 기억으로 남았습니다. 프로젝트 기간이 길어진 탓인지, 아니면 팀 인원이 늘어난 영향인지 정확히는 모르겠지만, 회고를 하면서 잦은 결정 번복이 가장 큰 어려움이었다는 것을 깨달았습니다.

기획 단계에서 다섯 명의 다양한 의견을 조율하는 것부터 많은 에너지가 소모되었고, 구현 단계에서도 이미 완료된 기능에 대해 다시 의문이 제기되는 경우가 종종 있었습니다. 이는 각 팀원이 중요하게 생각하는 관점이 다르기 때문이라고 생각합니다. 협업 과정에서 결정 번복은 효율성을 저해하는 주요 요인이 될 수 있다는 점을 인지하고, 앞으로 의견을 조율할 때 충분한 논의와 공감대 형성을 통해 결정 번복을 최소화해야 할 것입니다.

이번 티마고치 프로젝트를 통해 기술적인 성장뿐만 아니라, 팀워크의 중요성과 협업 과정에서의 어려움 및 이를 극복하는 방법에 대해 깊이 있게 고민해 볼 수 있었습니다. 값진 경험을 함께한 팀원들에게 감사하며, 앞으로의 프로젝트에서는 더욱 성숙한 자세로 임할 수 있도록 노력할 것입니다.