
나 자신이라는 이름의 서비스.
나에 대해 스스로 설명해보고, 또 타인에게 나를 설명해달라고 부탁함으로써 나를 좀 더 객관적으로 볼 수 있게 해주자는 의도에서 시작한 서비스.
앞선 두 프로젝트와 달리, 자유 주제로 진행한 프로젝트였다.
그러다 보니, 처음 기획부터 해야 했고, 디자인, 백엔드 api도 전혀 진행되지 않은 상황에서 한 달 안에 프로젝트를 완성해야 했다. 2주가 늘어났지만, 기획/디자인/백엔드 협업을 동시에 진행해야 하다보니 앞선 두 프로젝트보다 훨씬 개발 시간이 부족했다.
노션과 슬랙을 메인으로 사용했다.
백엔드, 프론트, 기획의 경우 노션에 채널 페이지를 생성해 회의록을 포함한 다양한 문서를 관리했고 디자이너분과는 피그마로 소통했다.

compound component pattern을 사용하려고 노력했다.
앞선 더줄게 프로젝트에서 아래와 같이 하나의 컴포넌트가 너무 많은 prop에 의존하는 문제가 있었다. 이번 프로젝트에서는 이런 문제를 해결하고 싶었다.

내가 맡은 부분인 RadarChart에 적용해봤는데, 원래라면 아래에서 .DefaultPolygon, .DraggablePolygon 컴포넌트가 받을 prop을 Chart 컴포넌트가 전부 주입받는 코드가 되었을텐데, dot으로 연결함으로써 prop을 나눠 가지게 만들었다. 의존성을 역전했다.


Trunk-Based Development를 선택했다.
프로젝트를 본격적으로 시작하기 전에 특강에서 TBD를 배웠다. 바로 적용해봤다.
TBD를 사용하기 위해 2가지 조건이 필요했는데,
하나는 신뢰할만한 CI/CD였고 다음은 인터페이스 설계를 통해 conflict를 최대한 막는 것이었다.
vercel로 배포할 생각이었고, vercel의 강력한 Ci/Cd는 신뢰할만 했다.
문제는 인터페이스 설계였는데, 이는 프로젝트 마감 기한을 못 지킬 것 같아서 페이지 단위로 역할을 나눠 최대한 작업 영역이 겹치지 않도록 해서 main 브랜치를 항상 배포 가능하도록 두었다.
TBD 덕분에 하루에도 수 차례 PR이 올라와 빈번하게 코드 리뷰를 해야 했다. 하지만, 이전보다 PR 단위도 줄였고 수명이 짧은 feature를 운용함으로써 하나의 PR이 가지는 작업 의미가 명확했다. 덕분에 코드리뷰가 쉬웠다.
그리고 squash merge 방식을 사용해서 main 브랜치의 커밋들을 관리했다. 아무래도 main 브랜치를 항상 배포 가능한 상태로 두려다 보니 커밋들을 좀 더 효율적으로 관리할 필요가 있었다.
next js의 app router를 사용하면서 서버 컴포넌트를 적극적으로 사용할 때 tanstack query의 dehydrate이 유용했다.
백엔드와 api를 협의하면서 페이지 단위로 필요한 데이터를 get 해오는 걸로 합의했다. 그러다보니 프론트 측에서 페이지 컴포넌트에서 데이터를 받아와, 해당 데이터가 필요한 클라이언트 컴포넌트까지 꽤 깊었다.
이를 단순히 props로 서버에서 불러온 데이터를 전달하려면 불필요한 prop drilling이 발생할 것이 명백했다. 이때 dehydrate을 적용했다.
서버 컴포넌트에서 데이터를 받아온 걸 dehydrate에 저장한다. 그럼 하위에 존재하는 아무 클라이언트에서 useQuery를 사용해서 동일한 key로 api 요청을 하면, 클라이언트 측에서 네트워크 요청을 하기 전에 캐싱된 데이터가 있으면 그걸 가져온다.
이를 통해 서버 컴포넌트 - 클라이언트 컴포넌트 간 불필요한 prop drilling을 막는 식으로 서버 상태를 관리했다.


제일 어려웠고, 프로젝트 처음부터 끝까지 나를 괴롭혔던 Radar Chart다.
원래 Chart.js 같은 라이브러리를 사용하려 했으나, 우리 기획의 요구사항 특성상 그래프에 rotate을 적용하거나, 드래그 이벤트를 걸고, 이를 통해 유저가 입력한 데이터를 db에 저장해야 했다.
다양한 이벤트를 걸고, 애니메이션까지 적용하려니 Chart.js로는 커스터마이징에 한계가 있다고 느꼈다.
게다가 드래그를 통해 유저가 본인의 액션이 UI에 반영되어야 했다. 하지만 이를 상태로 관리하기에는 너무 사소한 렌더링이 많아질 것 같았다.
그래서 유저가 드래그 중에는 svg를 조작해 보여주고 드래그가 끝났을 때 해당 데이터를 setState로 가져와 외부에서 사용하도록 했다.
d3.js는 svg 핸들러가 다양해서 이 덕분에 요구사항에 맞게 구현할 수 있었다.
다만 svg를 다루다 보니, 고등학교 때 배우고 까먹었던 sin, cos을 다시 배워야 했다. 그래프를 그리면서 느낀 건데 시각적으로 구현할 때 좌표에 친숙한 게 정말 중요한 것 같다. 프론트 쪽 애니메이션의 상당 부분은 좌표를 얼마나 디테일하게 다룰 수 있는지로 결정나는 거라고 느꼈다.
소셜 로그인과 프로젝트 전반의 프론츠 측 권한을 담당했다.
백엔드로부터 발급받은 리프레쉬 토큰과 엑세스 토큰을 next 미들웨어에서 처리했다. 쿠키에 저장하고 axios interceptor에서 토큰을 주입해주었다.
엑세스 토큰 재발급 api가 프로젝트 막바지에 나와서 적용하지 못했는데, interceptor에서 재발급 api를 부여할 계획이었다.
원래 소셜 로그인은 프론트 측에서 인가 코드를 핸들링한다. 이번 프로젝트에서는 일반적인 플로우와 달리 백엔드 측에서 인가 코드를 핸들링했는데, 별로였다. 아무래도 페이지를 담당하는 건 프론트 측이다 보니, 인가 코드를 백엔드가 핸들링 했을 때, 흐름이 자연스럽지 못했다.
아무래도 api 설계부터 같이하는 백엔드와 협업이 처음이었고, 소셜 로그인 경험이 없던 탓에 아무 생각없이 괜찮을 거라 생각했는데 역시 프론트 측에서 핸들링하는 게 맞았다.

framer-motion을 사용했다. framer-motion은 스크롤 애니메이션을 사용할 때 참 편한 것 같다. 관련 훅들도 많다.
왼쪽에 소개 글과, 오른쪽에 이미지를 보여주는 부분에서 recoil을 사용해서 상태 관리를 해야 했다. 왼쪽과 오른쪽의 움직임이 지금 구현한 건 똑같았지만, 각자의 애니메이션이 방식이 달라질 경우를 대비해 확장성을 고려하면 전역 상태로 관리해야 한다. 이 확장성 부분을 고려하느라 랜딩 페이지 제작이 오래 걸렸던 것 같다.
팀원들이 크로마틱을 통해 스토리북을 배포하고, PR 시 자동 배포되도록 github action을 작성했다. 덕분에 매 PR마다 코드만 보지 않고 시각적으로도 컴포넌트를 확인할 수 있어 코드 리뷰가 훨씬 생산적이었다.
그리고 또 Radar Chart를 만들면서 편리했다. Radar Chart를 거의 모든 페이지에서 사용해야 했다. 그러다 보니 각 페이지마다 주입해줘야 하는 prop이 달랐는데, 스토리북을 사용하지 않았다면 비효율적으로 노션에 정리를 하거나 회의때 말해줘야 했을 것이다.


