중간 프로젝트 결과물 혼콕 보러가기
드디어 9월 한 달 동안 프론트엔드끼리 진행한 중간 프로젝트가 끝났다. 프로젝트 주제는 소셜 네트워크 서비스였다. 기본적인 서버 API가 제공되며 그 API들을 활용해서 자유롭게 프로젝트를 개발하면 됐다.
팀 생성이 된 직후에는 프로젝트가 SNS 기반으로 만들어야 된다는 것을 알지 못했다. 그래서 팀원들끼리 하고 싶은 주제들을 미리미리 생각해놓자고 얘기를 나눴었고, 실제로 SNS에 국한되지 않는 다양한 주제들로 생각을 했다. 이후에 SNS 주제로 정해진 것을 확인하고, 프로젝트 첫 회의 전까지 관련 주제들을 부랴부랴 생각해오기로 했다.
첫 회의에서 서비스 주제, 기술 스택, 팀 문화, 목표 등에 대해 얘기를 나눴다. 회의 전에 팀 노션에 각자 고민해온 것들을 정리해오기로 했다. 나는 학교 수업을 들을 때부터 많은 아이디어를 다뤘기에 그 중 괜찮았던(SNS에 적합한) 아이디어들을 적었다. 나중에 내 아이디어 중 하나인 자취러 SNS 서비스가 뽑히게 되었다.
프로젝트명을 고민하다가, GPT한테 어울리는 이름 리스트들을 알려달라고 요청했다. 기대는 안 했었는데, 추천 리스트 중에 갑자기 '혼콕'이라는 단어가 한눈에 쏙 들어왔다. 모든 팀원이 다 그 이름에 동의했고 곧바로 '혼콕'은 우리 서비스명이 됐다.
혼콕은 혼자 콕 찌르기
의 줄임말로, 혼자 사는 사람들이 콕 찔러서 사용자들끼리 서로 소통하며 소중한 인연을 만든다는 뜻이 담겨있다. 우리는 혼콕 서비스를 떠올리며 주어진 api 기능들을 전부 활용할 수 있을 것 같다는 생각을 했다.
주요 api들은 다음과 같다.
예를 들어 '도와주세요'라는 채널을 만들면, 거기서 벌레를 잡아달라는 글이나 조명을 갈아달라는 글에서 사용자들끼리 댓글도 주고 받고, 메시지도 주고 받을 수 있다는 생각이 들었다.
팀 결성 처음부터 프로젝트 디자인과 PPT 디자인은 내가 맡겠다고 선언했다. 물론 먼저 피그마나 디자인 툴 다룰 줄 아는 사람 있는지 물어봤다. 내가 전공이기도 했고, 다른 분들은 디자인 경험이 거의 없다고 하셔서 그냥 맘 편하게 디자인을 맡겠다고 했다. 이전에 피그마로 학교 수업 때 낸 프로젝트들이 큰 도움이 됐다. 폰트, UI 사이즈 같은 것도 많이 참고했고 아이콘들도 몇 개 복붙할 수 있어서 편했다.
그동안은 기획, 디자인, 프로토타입 단계까지 그쳤었는데, 이제는 직접 프론트엔드 개발까지 경험해보니까 색다르게 느껴졌다. 개발 프로젝트에서 내가 디자인한 걸 내가 구현할 수 있는 게 신기했다.
원래 초반 며칠에 바짝 디자인을 완성하고 개발에 몰두할 생각이었지만, 그냥 이것 또한 애자일 느낌으로 해당 스프린트에 필요한 디자인을 그때그때 완성시키는 것으로 변경했다. 하다보니 느낀 게, 디자인은 빨리 끝낼수록 나중에 피곤하지 않다는 것을 깨달았다. 자세한 이유는 아래에 더 자세하게 쓸 예정이다.
기술 스택은 이전에 얘기 나눴던 스토리북, eslint 규칙, Next.js 등에 대해 좀 더 알아본 후 어떤 생각을 갖고 있는지 적었다. 스토리북 같은 경우에는 중간 프로젝트처럼 큰 규모가 아닌 프로젝트에는 굳이 필요할까? 싶었지만, 컴포넌트를 하나하나 직접 구현해서 재사용할 때 많은 도움이 될 것 같기도 했고 공부 겸, 연습 겸 도입하면 좋을 것 같다는 생각을 했다. eslint나 prettier는 크게 신경을 쓰지 않는 부분이라서 팀원들의 의견을 따라가기로 했다. 그리고 대망의 Next.js의 경우에는, 한 달 안에는 학습할 시간이 부족하다고 판단해서 이번 프로젝트에는 도입하고 싶지 않다는 의견을 적었다.
회의 결과는 대부분 내가 미리 생각해갔던 대로 결정됐다. 팀원 대부분이 다 비슷비슷한 생각을 갖고 있었다.
큼지막하게 결정한 기술 스택들은 다음과 같다.
React Query
: 서버 데이터 상태 관리React Router
: 페이지 관리Storybook
: 컴포넌트 관리tailwind
: 스타일vercel
: 배포 관리그리고 eslint의 경우는 rushstack의 rule을 적용시키고, 프로젝트에서 너무 빡빡한 규칙이 있으면 그때그때 false나 ignore로 rule을 제거하자는 식으로 결정했다.
협업 관련된 문서화나 프로젝트 관리는 전부 깃허브를 통해 진행하기로 했다. 이전에 플레이그라운드 리포지토리를 활용했기 때문에 큰 어려움이 없었다. PR과 이슈 템플릿을 정하고, merge는 1명 이상 승인을 받아야 가능하도록 결정했다.
그리고 브랜치는 github flow 전략을 응용하여 main
- develop
- feature
형태로 정했다.
merge 전략
feature
→ develop
: squash and mergedevelop
→ main
: create a merge commit코드, 이슈, 커밋 컨벤션 같은 경우도 하나하나 세부적으로 다 정했다. 해당 내용은 깃허브 위키에 자세하게 기록해놓고, 언제든지 헷갈릴 때 보고 이해할 수 있도록 했다.
일정 관리 칸반보드도 깃허브의 Projects를 활용해서 issue와 바로 연결되게 했다.
이후 팀 목표와 개인 목표를 정했다. 나는 개인적으로 React Query
를 좀 더 잘 이해하고, 리액트 자체를 이해하는 걸 목표로 두었다. 학습 외의 부분에서는 첫 프로젝트이다 보니, "1인분을 하자", "커뮤니케이션을 잘해보자"라는 목표를 두게 됐다.
"크게 활약을 해보자", "팀원들에게 큰 도움을 주자"라는 목표를 세울 수도 있었지만, 첫 프로젝트인 만큼 무사히 성공적으로 내 역할을 다 해내자는 마음부터 먹게 되었다. 그리고 팀원들과 더 가까워지고 커뮤니케이션을 잘 할수록 서로 도움도 잘 주고받지 않을까 하는 생각이 있었다.
주제를 정한 후, 브레인스토밍은 피그잼을 통해 진행했다.
머리 속에 있는 것을 고민하지 않고 모두 끄집어내서 만들어서 조금 지저분한 형태가 됐다.
피그잼에서는 어떤 페이지가 필요할지, 그리고 사용자 입장, 즉 유저 스토리를 통해 흐름이 어떻게 이어질까 하는 것들을 각자 정리했다. 또한 메인 페이지 같은 경우 UI가 어떻게 배치되면 좋을지 각자 시간 안에 그려보고 투표를 통해 뽑는 시간을 가졌다. 보다 보니 각자가 그린 UI들이 너무 필요한 것들이라서 결국 모두의 아이디어를 조합해서 구성하자는 결론이 나왔다.
프로젝트 개발이 시작되고, 우리는 초반에 기반을 잘 다져서 나중에 코드를 구현할 때 쉽게 쉽게 가자는 얘기를 했다. 먼저 vite 기반으로 프로젝트를 세팅 후, 사용하기로 했던 storybook이나 tailwind, eslint, prettier와 같은 기본적인 것들을 세팅했다.
src 폴더 내의 디렉토리 구조는 다음과 같이 나누었다.
📦api
├── index.ts
└── snsApiClient.ts : axios create
📦asset
├── 📦icons
├── 📦images
└── index.ts
📦components
├── 📦common
└── 📦domain
📦hooks
📦mocks
└── 📦components : 스토리북 mock
📦pages
📦routes
📦services : api 함수를 다루는 폴더
📦styles
├── 📦fonts
└── index.css
📦types
📦utils
─ App.tsx
─ main.tsx
─ vite-env.d.ts
기본 세팅 후에는 스토리북을 활용해 common 컴포넌트를 만드는 팀원들과, api 기본 기능을 구현하는 팀원들로 나뉘었다.
팀원 한 분이 스토리북 구현을 쉽게 할 수 있게 mock 파일을 만들어주셨다.
// MyComponent.stories.tsx
import { Meta, StoryObj } from '@storybook/react';
import MyComponent from './MyComponent';
const meta: Meta<typeof MyComponent> = {
title: 'Mocks/MyComponent',
component: MyComponent
};
export default meta;
type Story = StoryObj<typeof MyComponent>;
export const Example: Story = {
render: () => (
<div>
예제 컴포넌트
<MyComponent />
</div>
)
};
// MyComponent.tsx
const MyComponent = () => {
return <div className="text-teal-500">MyComponet</div>;
};
export default MyComponent;
스토리 파일을 만들 때 해당 템플릿을 바로 복붙해서 활용할 수가 있었다. 기본 컴포넌트인 Button
컴포넌트 같은 경우에는 다음과 같이 구현했다.
import { PropsWithChildren } from 'react';
interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
theme?: 'main' | 'active' | 'default';
size?: 'xs' | 'sm' | 'md' | 'lg';
variant?: 'solid' | 'outline';
}
export const Button = ({
theme = 'default',
size = 'md',
variant = 'solid',
children,
className,
...props
}: PropsWithChildren<ButtonProps>) => {
// 커스텀 속성 생략 ...
return (
<button
className={`${defaults} ${sizes[size]} ${variants[variant]} ${className}`}
{...props}
>
{children}
</button>
);
};
export default Button;
tailwind로 스타일을 적용했기 때문에 props로 className을 받아온다. 그리고 defaults 값과 받아온 className을 적용해줬다. 또한 기존의 button 태그의 속성을 그대로 받아쓰기 위해(자동 완성 기능을 활용하기 위해) 타입은 버튼 속성 타입(React.ButtonHTMLAttributes<HTMLButtonElement>
)을 상속받아 정의했다.
먼저 api
폴더 내에 있는 snsApiClient
파일에서 axios를 직접적으로 다룬다. services
폴더에 있는 각각의 service 파일들은 각 페이지 파일에서 api 기능 사용을 더 쉽게 할 수 있는 메서드들을 제공하도록 구현했다.
처음에는 그냥 객체 내에서 get
이나 create
같은 함수만 두고, 사용하는 쪽에서 직접적으로 리액트 쿼리를 사용하도록 구현했었다. 그러나 이것 또한 불필요하다고 느껴 아예 service 쪽에서 리액트 쿼리도 처리하도록 코드를 추가했다.
예를 들어 알림을 읽음 처리하는 api 기능은 아래와 같이 구현했다.
// notificationService.ts
const putNotificationsSeen = async () => {
return snsApiClient.put('/notifications/seen');
};
export const usePutNotificationsSeen = () => {
return useMutation({ mutationFn: putNotificationsSeen });
};
// NotificationsPage.tsx
const { mutate: putNotificationsSeen } = usePutNotificationsSeen();
// ... 이후 사용
커스텀 훅으로 만든 후에 페이지 파일 내에서는 그저 불러와서 사용만 하면 되니까 코드 구현이 간편해질 수 있었다. 또한 mutate
나 data
, isLoading
같은 값들을 분해 할당으로 가져오고 필요에 따라 이름도 명확하게 바꿔 사용할 수 있어서 이런 점들만 숙지하면 기능 사용이 훨씬 간단했다.
관련된 내용은 역시 위키(기술 스택)에 정리해놓았다.
초반에는 그냥 특정 기간 내에 할 일들을 정리하지 않고 그때그때 필요한 이슈들을 생성하고 각자 맡아서 올리자는 느낌으로 진행했다. 그러나 프로젝트 진행 이후 첫 커피챗 때, 멘토님께서 일정 관리를 좀 더 명확하게 하면 좋겠다는 피드백을 주셨다. 예를 들어 우리가 유저 스토리를 정했다면, 그 유저 스토리를 빅 티켓으로 두고, 거기서 구현해야 할 것들을 스몰 티켓으로 구분해서 이슈를 나눠가지면 어떻겠냐는 의견을 말씀해주셨다.
그래서 우리는 2차 스프린트부터 유저 스토리
라는 라벨을 추가해서 빅 티켓 이슈를 새롭게 만들었다.
이렇게 정리하니 해당 스프린트에서 무엇을 끝내야 하고, 앞으로의 스프린트에서는 어떤 것들을 구현해내야 할지 미리 파악할 수 있었다.
프로젝트를 하는 중에도 팀원들은 플레이그라운드를 적극 활용했다. 나같은 경우에는 배포 관련 깃허브 액션을 실험해보느라 플레이그라운드를 활용했었다. 원본 리포지토리는 배포가 불가능해서 내 개인 리포지토리로 fork해서 해당 리포지토리를 vercel에 배포했다. 멘토님의 첫 피드백 이후에 자동으로 배포 처리가 되는 기능을 구현하고 싶었다. 그러기 위해서는 원본 리포지토리가 merge가 되면, 자동으로 fork sync가 업데이트 됐어야 했다.
관련 정보를 검색 후, 이것저것 깃허브 액션을 건드려보며 연습하기 위해 플레이그라운드에서 아직 머지가 안된 PR들을 마구 머지시키며 실험을 했다.
결론적으로는 정해진 시간에 업데이트가 자동으로 되게 하는 건 성공했는데, 실시간으로 업데이트 하는 건 성공하지 못했다. 그래서 일단 이건 부수적인 문제니까 깔끔하게 포기하고 개발에 다시 열중하기로 했다.
다른 팀원 분들도 모달 구현이나 충돌 관련 실험을 하기 위해 플레이그라운드에 이것저것 구현해보고 PR을 날렸다. 플레이그라운드가 정말 큰 도움이 됐다고 생각한다.
처음부터 디자인을 내가 맡겠다고 한 터라, 초반에는 디자인을 하느라 큰 역할을 맡지 못했다.
예를 들면 앞에서 언급했듯 리액트 쿼리에 대한 service 코드의 초반 구현이나, 스토리북 세팅과 같은 작업은 다른 팀원분들이 진행해주셨다. 나는 해당 코드의 컨벤션을 익히고, 이후에 적용하는 역할만 맡아서 그게 조금 아쉽다. 우리는 이슈를 전부 나열해놓고 각자 가져가서 본인으로 Assignees을 할당하다 보니, 디자인에 열중하느라 큼지막한 틀을 정하지는 못했기에 그게 좀 후회가 된다.
그래서 디자인을 어느정도 하고난 이후에는 나도 이슈를 미리 다 만들어서 Assignees를 나로 할당해놓고, 잠을 줄여가면서 디자인과 개발을 병행했다. 결론적으로는 개발적인 측면에서만 봐도 1인분은 했다고 생각이 든다. 내가 구현하거나 맡은 역할들을 리스트로 나열해보면 다음과 같다.
- axios 인터셉터 구현 (토큰 검사)
- dayjs 라이브러리 추가 (위키 문서화)
- vercel 라우팅 관련 설정 (이후 수정)
- 프록시를 통한 엔드포인트 숨기기 (이후 수정)
- 로그인 api 기능 초반 작업
- 특정 포스트 상세 api 초반 작업
- 포스트 상세 보기
- 내가 작성한 포스트 수정
- 내가 작성한 포스트 삭제
- 특정 포스트 좋아요
- 특정 포스트 좋아요 취소
- 알림 api 기능 작업
- Footer 컴포넌트
- 가로 스크롤 컴포넌트
- Menu 컴포넌트
- 메인 페이지
- 404 페이지
- 알림 페이지
- 게시글 페이지 리팩토링
- favicon 추가
- 스켈레톤 UI 적용
- 전체 너비 768px 적용
- 전체적인 스타일 수정
뭔가 엄청 많은 작업을 맡아서 진행한 것 같지만, 코드 한 줄 짜리의 간단한 이슈도 있고 이후에 문제가 생겨 변경된 이슈들도 있다. 그나마 뒤에 여러 페이지들을 맡아서 기능과 스타일을 구현한 게 다행이라는 생각이 든다.
내가 겪은 트러블 이슈 중 가장 까다로웠던 건, 의외로 가로 스크롤이었다. '하나를 해결하면 또 하나가 터지고'의 상황을 그대로 겪었다.
처음에 가로 스크롤이 여러 곳에서 쓰이는 것을 알고, 가로 스크롤 컴포넌트를 만들어서 적용시키면 되겠다고 생각을 했다. 그리고 HorizontalScroll
이라는 컴포넌트를 스토리북도 활용해서 만들었다.
만들고 나니 몇 가지 문제가 있었다.
해당 문제들은 해결 방법을 찾아서 위키의 트러블 이슈에 자세하게 정리해놓았다.
가로 스크롤 말고도, 앞서 언급한 깃허브 액션의 자동화 배포 관련해서도 문제가 있었다. 결국 이 문제는 해결하지 못했지만, 프로젝트에 큰 영향은 끼치지 않았기에 추후 더 알아볼 계획이다. 그 이후 내가 문제를 겪었던 부분은 스켈레톤 UI를 적용했던 부분이다.
UI들의 크기를 정할 때 반응형을 고려해서 따로 width나 height를 지정해주기 보다는, 내부 content 크기에 맞게 정해지도록 스타일을 적용했었다. 그래서 그냥 padding 값만 지정해줬었다. 이렇게 하니까 스켈레톤 UI를 적용할 때 크기를 동일하게 맞추는 게 어려웠다.
임시방편으로 최대한 비슷한 크기로 맞춰서 적용을 해서 문제를 해결하려고 노력했다. 나중에는 사이즈나 위치도 고려해서 스켈레톤 UI를 구현해야겠다는 생각을 했다. 그리고 기존에는 그냥 <div>
태그들을 활용해서 구현했었는데, 찾아보니 <svg>
태그를 활용해서 스켈레톤 UI를 구현한 예시도 있다는 것을 알았다. 마침 데이터 강의를 들으며 <svg>
에 대해 학습한 게 있어서(아직 강의를 다 듣지 않았지만) 이걸 활용해봐도 좋을 것 같다는 생각이 든다.
프로젝트를 완성하기 직전까지도 피그마와 다른 부분들이나 동작이 이상한 부분들을 찾으려고 노력했다. 막바지엔 팀원 한 분께서 '효리님 이제 그만 찾아주세요..'라고 말씀하시기도 했다. 그정도로 완성도를 높이기 위해 눈에 불을 키고 이것 저것 눌러보며 테스트했다. 내가 주로 찾은 부분은 스타일이 다른 부분이었다. 예를 들어 모달에는 폰트가 적용되어 있지 않았다던가, 피그마와 폰트 사이즈가 다르다던가, 색상이 다르다던가 하는 부분이었다. 팀원분들은 다들 차이를 모르겠다고 하셨지만, 전공자의 시각으로는 눈에 너무 잘 띄어서 신경이 쓰였다(..)
그래서 그런지 제출 당일날 밤에 매니저님께서 우리 서비스를 이용해보셨는데, 다른 팀들의 서비스에 비해 고칠 피드백이 거의 없다고 하셔서 뿌듯함이 컸다. 물론 그럼에도 팀 상영회 날에 미처 발견하지 못한 예외 케이스들이 몇 군데 발견되긴 했다. (팀원분이 바로 수정했다.)
프로젝트를 진행하면서 갑자기 에러가 뜨거나 의도대로 동작하지 않던 부분들도 있었지만, 곧바로 원인을 찾아내고 해결했다. 말 그대로 수월하게 진행했다. '진짜 망했다', '큰일났다' 하는 상황들은 없었던 것 같다. 생각해보니 이전부터 플레이그라운드도 활용하고, 깃허브도 공부하고, 관련 내용들을 미리미리 학습해놓아서 문제 해결이 빨랐던 것 같다. 그리고 좀 어려운 부분들이 있다 하면 팀원들한테 공유하면 정말 빠르게 해결방안을 제시해주거나 관련 자료를 찾아주셔서 그냥 문제 상황을 "순삭"했다.
리액트 쿼리에서도 많이 헤맬 줄 알았는데, 팀원 분이 공유해주신 깃허브 링크가 너무 잘 정리되어 있어서 큰 어려움이 없었다. 아무래도 프로젝트 시작 전에 이 자료를 정독해서 그런지, 팀원들의 코드를 이해하는 속도도 비교적 더 빨랐던 것 같다.
Keep
이전보다 공식 문서를 읽는 게 더 편해져서 코드를 이해하고 구현하는 속도가 좀 더 빨라졌다. 이는 지금까지 해온 자바스크립트 딥다이브 스터디와 타입스크립트 스터디, 플레이그라운드 docs 발표 등의 결과물이라고 생각한다. 완벽하게 암기하며 공부하지 않더라도, '아 이런 개념을 공부했었지', '이거 책에서 나왔는데!' 하면서 찾아볼 수 있어서 어렵지 않았다.
또한 팀원들이 너무 좋았다! 분위기도 좋았고, 피드백이 있으면 다들 바로바로 수용했기 때문에 지금 보기에는 우리가 처음에 정했던 팀 목표인 "한 명이 작업한 것처럼"을 어느정도 달성했다고 생각한다.
Problem
다크모드를 적용하지 못한 게 아쉽다. 코드는 다 구현해놔서 색만 입히면 되는 거였는데, 디자인 시간이 부족해서 적용하지 못했다. 이건 디자인만 나오면 바로 적용할 수 있는 문제라서 추후에 적용할 수도 있을 것 같다.
그리고 문서화를 열심히 하자고 노력은 했지만, 이후에는 실시간으로 메모하는 습관도 들이면 더 좋을 것 같다는 생각이 들었다. 네트워크 요청에 대한 시간이나, UI 변화 이런 것들을 전후 상황을 캡쳐해놓거나 gif로 비교하면 한눈에 보기도 쉽고 나중에 정리하기도 편할 것 같다.
프로젝트에 묻혀서 소홀히 했던 JS 딥다이브 책도 다시 쉽게 정리해놓을 생각이다. 원래는 스터디에서 내 발표 차례가 아니어도 TIL에 열심히 정리해놔서 잘 까먹지 않았었는데, 프로젝트를 하면서 책도 허겁지겁 읽고 문제도 대충 풀어서 기억에 잘 남지 않았던 것 같다. 이번 주에 끝나는 스터디지만(길고 길었다) 이 책은 앞으로도 여러 번 다시 정독해야겠다고 느꼈다.
Try
최종 프로젝트에서는 Next.js를 도입해보기로 했다. 팀원 한 분이 중간 프로젝트에서 Next.js로 개발하셨기도 했고, ssr과 ssg에 대해 좀 더 딥하게 공부하고 싶기도 하고, 2차팀 때 Next.js 개념에 대한 팀원분의 발표 내용을 이해하지 못한 게 아쉽기도 하고, 관련 도서를 구매했는데 2달 간 한 쪽도 읽지 못했기도 하고, 이 모든 이유를 종합해서 Next.js를 열심히 공부해보고 싶다는 생각이 들었다.
또한 이제 이력서를 써야한다는 게 실감이 나기 시작했다. 슬슬 이력서를 작성하고, 내 포트폴리오를 정리할 필요가 있다고 느꼈다. 10월 한 달 동안은 이력서 작성에도 시간을 쏟을 예정이다.
곧 나트랑 여행을 갈 예정인데, 갔다온 이후에는 잠을 더 줄이고 여가 시간을 더 줄여서 프로젝트와 이력서에 많이 집중하고 싶다. 개인적으로는 디자인도 더 예쁘게 만들고 싶다는 욕심이 있다. 이제 정말 수료까지 얼마 남지 않았기에 남은 시간 동안 잘 해내서 최종 프로젝트의 회고글도 뿌듯함만 가득했으면 좋겠다! 🔥
효리님! 9월 회고 잘 읽었습니다! Github 에 있는 기능들을 잘 활용하신 점과 브레인 스토밍을 피그잼으로 하셨다는 점이 놀라워요! 더불어서 효리님의 전공이 뭔지도 궁금해졌습니다...! UI/UX 관련 전공이시라면... 저도 효리님께 배우고 싶어요 🥹 다음주 목요일에 저희 팀이랑 저녁 같이 먹기로 했는데 그 때 효리님과 많이 친해질 수 있으면 좋겠어요~! 다음주부터 시작할 최종 프로젝트도 너무 기대되네요! 화이팅입니다 효리님!