[Side Project] Cookidge 회고

홍정민·2025년 1월 12일
0

Cookidge 서비스의 개발이 끝났다. 기능 구현, 배포, 검색 엔진 노출 등으로 마무리 지어졌고, 이제 가독성 좋은 코드, 성능 최적화, 기능 추가 등 유지 보수 작업을 기대하고 있다.

해당 프로젝트로 나의 개발 실력에 많은 도움이 됐다. 프론트 엔드, 백엔드 API 서버를 구현하고 배포를 하여 구글 검색 엔진에 노출 되도록 하는 기나긴 여정을 겪었다.

그래서, 이러한 긴 여정을 작성해볼 생각이다. 꽤나 완성도 있는 프로젝트라고 생각하지만, 이 글을 10년 후에 다시 읽게 된다면 귀여운 프로젝트란 걸 깨닫게 되겠지...

앱 개요


Cookidge는 Cook + Firdge의 합성어로, 냉장고를 관리하고 공유할 수 있는 냉장고 서비스와 요리 레시피 SNS를 결합한 음식 관련 통합 서비스이다.

개발 동기

일반적인 레시피 SNS는 "만개의 레시피", "우리의 식탁", "한식포털" 등 꽤나 존재했다. 이런 것들이 존재하는데 레시피 SNS 프로젝트를 만든다는 것은 이유 없이 그냥 만드는 것과 같다.

따라서, 나는 차별성을 도입하기 위해 냉장고 관리 앱까지 결합하여 레시피 SNS를 제공하자고 생각했다.

"나의 냉장고에 재료를 입력하고 이런 냉장고 상태를 기반으로 만들 수 있는 레시피를 제공해 주는 사이트라면 어떨까?"

이렇게 요리 테마에 관련된 다양한 서비스를 제공하자는 생각으로 만들게 되었다.

어려웠던 점

4개월이라는 기간동안 나 혼자서 학습하고 개발을 진행했기 때문에, 기존에 내가 알던 지식, 구글링, GPT에 의존하며 외로운 싸움을 했다. 프론트엔드만 개발 했을 때랑은 차원이 다른 난이도였다.

로그인

일반적인 앱에서 로그인 기능은 필수이다. 프로젝트에서 로그인 부분에 가장 많은 시간을 들였던 것 같다. Google OAuth와 함께 백엔드 인증을 구현했는데 코드 구현보다도 전체적인 흐름 로직을 어떻게 가져가야 하는지가 가장 어려웠다.

  • 토큰을 생성해서 쿠키에 등록을 프론트엔드에서 해야하는지 백엔드에서 해야하는지?
  • 매번의 요청에 FE는 어떻게 하고 BE는 어떻게 해야하는지?
  • 만료된 토큰을 재발급하는 흐름이 어떻게 되는지?
  • token을 어디에 저장해야 하는지?
  • Google OAuth랑 어떻게 연동하여 흐름을 구현해야 하는지?
  • OAuth를 사용하면 로그인과 회원가입은 동시에 작동하는가?

하면서도 "이게 맞는건가?" 라는 의문점을 갖은 것 같다. 이런 의문점들 말고도 겪었던 오류들도 생각하면 정말 굉장히 어려웠던 부분이다.

수많은 구글링과 GPT 그리고 구현한 코드의 타당성을 찾으며, 만족스럽게 구현이 된 것 같다.

데이터 모델링

나는 RDBMS에 친숙했기 때문에, NoSql은 막연했다. 읽기의 성능 이점을 가져가기 위해 선택했는데, 어떻게 데이터 모델링을 해야할지 많은 고민과 결정을 했다. 심지어 임베딩 모델링을 했다가 싹 다 갈아엎고 참조 모델링을 했다가 다시 임베딩을 했다가 하며 시간을 허비한 경험도 있다.

  • 어떻게 해야 최대한으로 $lookup을 피하면서 컬렉션을 구성하지?
  • 좋아요는 어떻게 모델링 해야할지?

데이터 모델링 부분에서는 확실히 경험이 중요한 것 같다.

레시피 Form 이미지

Cookidge 프로젝트에서 레시피 생성 폼에는 꽤나 많은 input이 들어간다. 여기서 특히 "요리 과정" 부분의 이미지에서 시간을 소모했다.

프론트엔드에서 이미지들을 base64로 인코딩하여 보내면 되는데, 굳이 사이즈가 33% 증가한다는 문제를 해결하려고 blob으로 전송하여 백엔드에서 multer로 굉장히 힘들게 처리한 경험이 있다.

사실, 백엔드에서 Cloudinary로 보낼 때 base64로 변환해서 보내기 때문에 의미없는 짓이라고 생각했었지만 서버리스의 요청 본문 크기 제한의 특징 때문에 그렇게 의미없는 짓은 아니라고 또 생각했다.

반응형 웹

다양한 화면 사이즈에서도 자연스럽게 보여질 수 있도록 767px 기준으로 모바일 화면도 생각하며 구현했다. 특히 flextext-overflow가 특히나 말썽이였다.

이 외에도 코드를 어떻게 깔끔하게 작성할지, 성능은 어떻게 개선할지 등의 다양한 고민을했다.

좋았던 점

FE 디자인 패턴

FSD 디자인 패턴에 관심을 갖다가, FSD를 적용한 좋은 예시의 프로젝트를 발견하게 되고 그것을 Cookidge에 적용시켰다.

덕분에 더 좋은 코드와 폴더 구조를 갖추게 되어 내가 원하는 컴포넌트가 어디 폴더에 위치해 있는지 추측이 더 쉬워졌다. 그래도 아직 부족한 것 같지만...

FE 성능 처리

이번 프로젝트에 좀 더 빠른 성능과 좋은 사용자 경험을 위해 다양한 시도를 했다. 이러한 시도를 통해 더 완성도 있는 프로젝트를 개발할 수 있었다.

  • 코드 스플리팅
  • Abort Signal
  • 이미지 최적화
  • Loading Spinner / Skeleton UI
  • 낙관적 업데이트
  • 무한 스크롤
  • 디바운스

백엔드 경험

어떻게 백엔드에서 api가 만들어지는지, 어떻게 인증이 흘러가는지 DB는 어떻게 사용되는지, 어떻게 쿼리가 작성되고 데이터를 추출하는지 등의 경험을 하여 시야가 좀 더 넓어진 느낌이다.

트러블 슈팅

검색 결과 URL이 History에 남는 문제

원인: 검색창은 쿼리 스트링을 갖고 GET요청을 한다. 뒤로가기를 눌렀을 때, 이전 검색으로 이동하여 많은 뒤로가기를 해야 검색창을 빠져나가지는 안좋은 UX를 제공했다.
해결: useNavigate, setSearchParams 등에는 History를 남기지 않은 속성이 존재했다. replace 속성을 사용하여 History에 이전 검색 기록을 남기지 않고 바로 빠져나올 수 있게 했다.

IOS, 사파리 등에서 로그인이 안되는 오류

원인: Cookidge의 FE와 BE의 도메인이 다르기 때문에 서드파티 쿠키를 제한하는 브라우저에서는 token이 쿠키에 등록되지 않음
해결 방안: 도메인을 구입하여 백엔드 api를 하위 도메인으로 통합 후 퍼스트 파티 쿠키로 발급하여 해결하는 방안이 있지만 비용이 들기 때문에 보류.
해결: 프론트엔드에서 프록시를 사용하여 마치 퍼스트 파티 쿠키처럼 동작하도록 구현했다.

모바일 크롬 환경에서 레시피 폼 요리과정 항목을 추가할 때, 미리보기 이미지가 없어지는 오류

원인: 이미지 미리보기를 구현할 때, useWatch 를 통해 모든 요리 과정 이미지들의 createObjectURL, revokeObjectURL을 한 번에 처리한다. 요리과정 항목을 추가하거나 삭제할 때, 모바일 크롬 환경에서 한 번에 많은 createObjectURL을 처리하면서 에러가 발생하는 것으로 보임
해결: PreviewImage 컴포넌트를 생성하고 React.memo를 적용하여 요리과정 항목이 추가/제거 될 때, 다른 컴포넌트가 리렌더링 되지 않게했다.

배포 환경 고화질 이미지 전송시 413 Error 발생

원인: Vercel 서버리스 요청 본문 크기 4.5MB 제한
해결: 이미지 base64대신 blob으로 전송하고, image-compression 라이브러리를 사용하여 이미지 압축

그러나 추가 에러가 발생하였다.

모바일 크롬 환경에서 이미지 압축이 안되는 현상

원인 추정: 폼을 제출할 때, 모든 이미지를 압축하는 로직이다. 그러나 압축할 때 File이 비어져있다.

  • ❌ 비동기 실행에 예기치 못한 에러를 방지하기 위해 이미지를 하나의 배열에 모아 Promise.all을 통해 한 번에 이미지를 압축하는 방법
  • map함수에서 await가 보장되지 않는다는 이론으로 for문을 사용하는 방법
  • 🔶 폼에 아무런 리렌더링도 일어나지 않은 경우에 압축에 성공했다. 그러나 useFieldArray의 항목을 추가/제거 했을 때, 해당 필드만 리렌더링이 발생하지만 이 경우에도 File이 비어지는 현상이 발생했다.

수많은 테스트 결과 폼이 리렌더링 되면서 File을 잃어버리는 것으로 추정된다. 이 문제에 너무 많은 시간을 소모하여 일단 해결을 미뤘다.

결론

풀스택 웹 개발을 통해 다양한 문제를 해결하고, 여러 가지 지식을 학습하며 값진 경험을 쌓을 수 있었다. 하지만 여기서 끝이 아니다. 현재 남아 있는 오류들을 모두 해결하고, 기회가 된다면 React Native나 Next.js를 적용해 보고 싶다.

0개의 댓글