시작하며...
코드잇 스프린트 6기 파트3에서 6월 21일부터 7월 9일까지 약 2주간 중급 프로젝트를 진행하였다. 프로젝트를 진행하면서 내가 어떤 것을 구현하고 고민했는지를 상기하고 다시 한번 돌아보기 위해 글을 작성하게 되었다.
https://github.com/wiki-viki/wiki-viki
프로젝트 선정 목적
프로젝트 선정 이유
지난 초급 프로젝트와 동일하게 이번에도 총 4가지의 주제가 있었다. 그 중 Wiked 라는 프로젝트는 지인들의 위키를 작성하고 공유하는 서비스를 팀원들과 오랜 시간 회의한 결과, 여러 가지 측면을 고려해 보았을 때 Wiked 프로젝트가 가장 좋을 거 같아 만장일치로 프로젝트를 선정하게 되었다.
조금 색다른 주제이기도 하였으며 그동안 프로젝트를 진행하면서 느낀 것이 유저를 모으기 위해서는 적은 인원이 상호작용을 해도 원활하게 운영되는 것을 느꼈고, Wiked 같은 경우는 주변 지인들로만 상호작용해도 충분히 서비스가 운영될 수 있다고 생각을 했다.
이런 기획적인 측면에서도 좋다고 생각이 들었고, 기술적인 측면에서도 4가지 주제 중에서 Wiked 프로젝트의 난이도가 가장 높아 새로 적용해볼 수 있는 기술이 있을 거 같아 마음에 들었다.
프로젝트 네이밍
회의를 하면서 즉석에서 위키비키 라는 아이디어가 나왔다! (어쩌다가 정했는지 기억은 잘 나지 않는다 😅 약간 의식의 흐름?) 요즘 유행하는 걸그룹 IVE 장원영의 초긍정적 사고방식인 원영적 사고 즉 럭키비키를 바탕으로 "다른 사람이 나에 대한 위키를 써준다니 완전 위키비키잔앙🍀" 라는 슬로건을 만들게 되었다.
그래서 이러한 네이밍에 맞게 기존 Wiked 로고를 참고하여 wiki viki 로고를 만들었으며, 그 외에도 프로젝트를 진행하면서 wiki viki 에 필요한 이미지들을 직접 제작하였다.
(canva 로 만듦! 상업적 이용이 가능하다 해서 저작권 문제없이 편안하게 만들었다!)
프로젝트 초기 세팅
프로젝트를 구현하는 것도 중요하지만 해당 프로젝트를 개발하기 위해 어떤 기술을 사용할 것이고 어떤 식으로 프로젝트를 진행할지 정하는 것이 중요하다.
사전 조사
프로젝트 주제가 나오기 전 주부터 각자 컨벤션이나 사용하고 싶은 라이브러리들을 선정하여 사전 조사를 하였다. 나는 아래와 같은 내용을 조사하였고 실제로도 거의 이대로 반영이 되어 프로젝트를 진행하게 되었다.
초기 세팅
지난 프로젝트에 이어 이번 프로젝트에서도 내가 초기 세팅을 하게 되었고 https://jjang-j.tistory.com/129 에서 확인할 수 있다.
기술 스택
우리는 크게 Next.js, TypeScript, Tailwind CSS, Zustand 를 사용하였다. 그 밖에 라이브러리로는 React-Hook-Form, Axios, Framer Motion, React Quill, React-md-Editor 등을 사용하였다.
Next.js
코드잇 강의로 꾸준히 배우던 Next.js 를 사용하였다. 파일 기반 라우팅이라 React 처럼 React-Router-Dom 이런 라이브러리를 사용하지 않고 간편하게 라우팅을 할 수 있다. 그 밖에도 Server Sider Rendering 이 가능해 로딩 속도도 개선이 되고 SEO 에도 좋은 프로젝트를 만들 수 있다.
그리고 Next.js 신버전인 App Router 대신 Page Router 를 사용하였다. App Router 는 대규모 프로젝트에 적합하며 많은 기능과 유연성을 제공하지만 구조가 복잡하고 러닝 커브가 길어, 소규모 프로젝트에 어울리고 간편한 Page Router 를 사용했다.
TypeScript
이전에 타입스크립트로 프로젝트를 하다가 초급 프로젝트에서 자바스크립트로 프로젝트를 진행하면서 타입스크립트의 소중함을 느꼈다.
타입스크립트의 타입 안정성으로 컴포넌트를 사용할 때 넘겨야 되는 Props 를 넘겼는지 안 넘겼는지 눈으로 쉽게 확인할 수 있다. 그리고 객체 속성을 사용할 때 자동 완성 기능이 있어 사용하기 편리하며 유지보수에도 좋고 팀 프로젝트에서 다른 사람이 작성한 코드에 대해서 한눈에 쉽게 파악할 수 있어 타입만 명시했을 뿐인데 문서화의 기능까지 제공해준다. 아무튼 타입스크립트 짱 ٩( ᐛ )و
Tailwind CSS
주변에서 Tailwind가 너무 편리하다 해서 한 번 정도는 사용해보고 싶었으면서도 저게 왜 편하지? 새로운 문법을 외워야 하고 className 에 지저분하게 스타일을 입력해야 하니깐 가독성도 떨어질 거 같은데... 라는 우려를 하고 있었다.
그러나 회의를 하면서 Next.js 와 Tailwind CSS 조합이 좋고 팀원 모두 Tailwind 를 사용해 본 경험이 없어 이번 기회에 한 번쯤 사용해 보면 좋을 거 같다! 라고 해서 도입하게 되었다.
일단 Tailwind CSS 는 스타일링을 다른 파일에서 하지 않고 바로바로 할 수 있고 className 명을 지을 수고가 사라진다. 그리고 반응형 스타일링도 매우 편했다. 특히 Tailwind CSS 는 필요한 className 만 최종 파일에 포함시키고 빌드된 CSS 크기가 작아져 SSR 등을 제공하는 Next.js 에서 페이지 로딩 속도가 빨라져 성능적인 측면에서도 좋다. 특히 커스터마이징에 용이하며 스타일을 기본테마에 확장시켜 사용할 수 있어 타이포그라피를 사용하는데 매우 유용했다.
Zustand
React 에 기본적으로 내장되어 있는 Context API 를 활용해서 상태 관리를 할 수 있지만, Context API 는 상태를 주입하는 거라 불필요한 렌더링이 발생한다고 한다. 그래서 Context API 보다는 전역 상태 관리 라이브러리를 사용하는 것이 학습적인 측면과 성능적인 측면에서 더 좋을 거 같아 선택하게 되었다.
다양한 상태 관리 라이브러리 중에서 Zustand 를 선택한 이유는 일단 큰 프로젝트가 아니다 보니 간편하게 사용하기 위해 러닝 커브가 낮고, 라이브러리의 업데이트가 꾸준히 되면서, 다운로드 수가 많은 Zustand 를 선택하게 되었다.
그리고 이전 프로젝트에서 Zustand 를 사용한 적이 있었는데 내가 맡은 페이지에서는 해당 내용을 사용하는 부분이 없어 아쉽게 사용하지 못했는데 이번 프로젝트에서는 제대로 사용하여 이에 대한 갈증을 해소해보고 싶었다.
공통 컴포넌트 만들기
프로젝트 초기 세팅을 마친 후, 바로 공통 컴포넌트 개발에 들어갔다. 개발을 시작하면서 공통 컴포넌트를 만드는 것이 가장 먼저 해야 된다고 생각이 든다. 진부하지만 아래와 같은 이유가 있다.
- 재사용성 향상
- 유지보수성
- 일관성 있는 UI/UX
- 코드 관리 용이
- 팀 협업 효율성
처음에 공통 컴포넌트를 만들어야 되는지 몰랐을 때, 한 페이지에 너무 많은 코드의 양이 있어 가독성이 낮았었다. 그리고 UI 하나가 바뀌면 이곳저곳에서도 수정을 해야 하니 유지보수성도 낮아지고 시간이 많이 소비되었다.
그래서 작년에 거의 처음으로 했던 팀 프로젝트에서는 바텀시트 컴포넌트가 2개가 있어 각자 만든 걸 사용하는 식으로 했었다. 그러나 다음 프로젝트에서는 미리 필요해 보이는 공통 컴포넌트를 만들고 팀원과 소통하면서 누가 어떤 것을 만들고 어떻게 만들었는지 공유를 하니깐 개발 시간도 단축할 수 있어, 이때 공통 컴포넌트의 중요성을 몸소 느끼게 되었다.
일단 나는 공통 컴포넌트를 만드면서 다음 원칙을 고려하면서 만들었다.
- 단일 책임의 원칙
- 재사용성
- 확장성
1. 모달 Modal 컴포넌트
가장 먼저 모달 컴포넌트를 만들었다. 기본적으로 이런 기능을 가지고 있다.
- 외부 영역 클릭 시 닫힘
- ESC 입력 시 닫힘
- Protal 사용해 DOM 구조 분리
- Framer Motion 사용
세로의 길이는 모달 내부 내용에 따라 유연하게 바뀌도록 하여 재사용성이 좋은 모달을 만들었다. 그리고 모달의 기본적인 틀을 만들었기 때문에 다른 팀원이 내가 만든 모달을 활용하여 ConfirmModal 을 새로 만들어 확장성이 높은 컴포넌트를 보장하였다.
useBoolean 훅 제작
모달을 만들면서 모달뿐만 아니라 드롭다운 등에서 사용할 수 있는 useBoolean 훅을 만들었다. 특히 useCallback 을 사용하여 성능 최적화를 하였다.
2. 검색창 Seach Bar 컴포넌트
다음으로 검색창 컴포넌트를 만들었다. 기본적으로 이런 기능을 가지고 있다.
- 일반 버전 vs 디바운스 버전
- 사용자가 입력할 때마다 호출되는 함수
- 부모 길이에 따라 길이가 변함
- input 기본 요소 속성 받음
interface SearchBarProps extends ComponentProps<'input'> {
placeholder: string;
onSearchItem: (keyword: string) => void;
isDebounce?: boolean;
}
디바운스 여부
사용하는 사람 입장에서는 편하지만 막상 컴포넌트를 보면 일반 버전이랑 디바운스 버전이 있어 조금 복잡해 보인다. 그래서 일반 버전 검색창과 디바운스 버전 검색창을 만들려 했지만 재사용성이 낮아 그냥 하나의 검색창에 isDebounce 를 props 로 받아 처리하였다.
input 기본 요소 상속
그리고 input 태그의 기본 요소 속성을 상속받아 재사용성과 확장성을 높였다. 그래서 위에 코드처럼 SeachBar 인터페이스에서 onKeyDown 이 없는데도 기본 input 태그이 속성을 받아 사용할 수 있게 된 것이다.
3. 헤더 Top Navigation Bar 컴포넌트
다음으로 헤더인 TopNavigationBar 컴포넌트를 만들었다. 사실 공통 컴포넌트라 보기 어렵지만 여러 페이지에 사용이 돼서 일단 공통 컴포넌트로 분리를 해보았다. 기본적으로 이런 기능을 가지고 있다.
- 반응형
- 로그인 여부에 따른 렌더링
- 알림창, 메뉴바
일단 반응형, 로그인 유무 등에 따라 보여줘야 할 내용이 너무 달랐다.
그래서 최대한 겹치는 부분은 컴포넌트로 쪼개고 최대한 효율적인 코드를 만들기 위해서 리팩토링에 신경을 많이 썼다.
useIsMobile 훅 제작
모바일에 따른 다른 내용을 보여주기 위해 useIsMobile 훅을 만들어 resize 이벤트 핸들러를 사용하였다.
로그인 여부
로그인 여부는 Zustand 를 활용하여 구현하였다. 관련 내용은 https://jjang-j.tistory.com/134 에서 확인할 수 있다.
페이지 기능 구현
공통 컴포넌트를 다 만든 후, 각자 맡은 역할 페이지를 개발하기 시작했고 나는 자유게시판 페이지와 게시물 작성 및 수정 페이지를 맡게 되었다.
1. 자유게시판 페이지
자유게시판의 기능은 다음과 같다.
- Server Side Rendering
- 검색, 최신순/좋아요 순, 페이지네이션
- Framer Motion 과 CSS 로 사용자 경험성 향상
Server Side Rendering; SSR
데이터를 서버 측에서 미리 가져와서 페이지에 props 로 전달하는 SSR 을 사용하여 구현하여 초기 로드 시간을 개선할 수 있었다.
그리고 Promise.all 을 사용하여 비동기 함수를 병렬로 호출하였고, 둘 중 하나라도 실패를 하면 에러가 발생하여 "/500" 에러 페이지로 리다이렉트 하였다.
// 베스트 게시물, 게시물 목록 가져오기 [SSR]
export const getServerSideProps = async () => {
try {
const [bestBoardList, boardList] = await Promise.all([
getArticle({ pageSize: 4, orderBy: 'like' }),
getArticle({ pageSize: PAGE_SIZE }),
]);
return {
props: {
bestBoardList,
boardList,
},
};
} catch (error) {
return {
redirect: {
destination: '/500',
permanent: false,
},
};
}
};
검색, 최신순 / 좋아요 순, 페이지네이션
이 부분은 CSR 을 사용하여 값이 바뀔 때마다 데이터 요청을 하도록 구현하였다.
Framer Motion & CSS
정적인 피그마 시안만 있었고, 사용자 경험성을 늘리기 위해 디테일하게 애니메이션과 CSS 를 적용하였다. 개발을 하는 것도 중요하지만 프론트엔드 개발자로서 사용자 경험성 측면에서 어떻게 구현하면 좋을지 고려하는 것도 매우 중요하다고 생각이 들어 이 부분에도 신경 쓰며 작업하였다. 특히 애니메이션 같은 경우 시간이 많이 소요될 수 있는데 이렇게 Framer Motion 라이브러리를 사용하여 간편하게 구현할 수 있어 너무 좋다.
모바일 버전
모바일 버전에서는 베스트 게시물을 React-Slick 라이브러리를 사용하여 캐러셀로 만들었다. 지난 프로젝트에서도 라이브러리를 사용하여 캐러셀을 만들었는데 다음에 기회가 된다면 라이브러리를 사용하지 않고 캐러셀을 직접 구현해 보는 것이 목표이다!
2. 게시물 등록 페이지
게시물 등록하기 페이지의 기능은 다음과 같다.
- React Quill 라이브러리로 에디터 기능
- 이미지 삽입 및 유효성 검사
- 이미지 사이즈 조절
- 게시물 글자수
- 등록한 게시글 내용 조정
- Zustand Persist
React Quill 라이브러리
여러 블로그에서 에디터 라이브러리 비교글을 보면서 커스터마이징도 쉽고 React 에서 사용하기 편한 React Quill 라이브러리를 선택하게 되었다. 실제로 사용해 보니깐 정말 커스터마이징이 쉬워 원하는 에디터 도구를 가져와 원하는 순서대로 배치할 수 있었고 직접 CSS 스타일링도 할 수 있어 피그마 시안대로 UI 를 구현하였다.
그러나 React Quill 는 document 객체를 조작하여 동작을 하는데 Next.js 는 기본적으로 SSR 이라 서버에서는 document 객체가 존재하지 않아 오류가 발생하게 된다. 그래서 React Quill 을 클라이언트 사이드에서 로드하여 실행하기 위해 dynamic import 의 ssr: false
속성을 주었다.
이미지 삽입 및 유효성 검사
이 부분은 작성할 내용이 많은 거 같아 새로운 글로 작성할 예정이다.
이미지 사이즈 조절
이미지를 그대로 삽입을 하면 너무 사이즈가 커 이미지 사이즈를 조절할 수 있는 기능이 필요했다. 해당 내용은 https://jjang-j.tistory.com/125 에서 확인할 수 있다.
게시물 글자수
처음에는 입력받은 값 그대로를 활용하여 글자수를 세는 함수를 작성했다. 에디터 라이브러리이기 때문에 입력받은 값은 HTML 태그가 포함되는 값이라 HTML 태그를 빈문자열로 대체하였다. 그러나 은근 하자가 있었고 그냥 입력한 글자 그 자체만 가져올 수 있는 방법이 없을까 하고 계속 구글링을 통해 좋은 방법을 찾아냈다.
const textOnly = value.replace(/<[^>]*>/g, '');
그래서 React Quill 에디터 내용이 변경될 때 호출되는 함수에서 editor 라는 에디터 내용과 상호작용할 수 있는 메서드를 사용하였다. 그래서 editor 의 getText 메서드를 사용하여 입력한 문자 그대로를 가져왔고 개행 문자도 포함되어 있어 이를 빈문자열로 대체하여 글자수 길이를 구하였다.
등록한 게시글 내용 조정
이미지 사이즈를 조절한 상태에서 제출을 하면 이미지 태그 인라인 스타일에 아래 마우스 커서가 적용이 되고, 이미지의 사이즈가 소수점 10자리 이상 넘어갔다.
그래서 마우스 커서 스타일을 없애고, 이미지 사이즈의 소수점을 조절하기 위해 함수를 작성하였다.
Zustand Persist
게시물 작성하기 기능을 구현하면서 Zustand 의 Persist 를 도입하였다. 화면에 유저의 이름을 보여줘야 하는데 API 요청을 하기엔 비효율적이라 판단이 들어 헤더 TopNavigationBar 를 구현하면서 사용했던 Zustand 를 활용했다. 이를 구현하면서 유저의 정보도 저장하여 유저의 name 만 가져와 활용을 했는데 이게 새로고침을 하면 값이 사라지는 현상을 발견해 Persist 라는 미들웨어를 사용하였다. 자세한 내용은 https://jjang-j.tistory.com/124 에서 확인할 수 있다.
3. 게시물 수정 페이지
게시물 등록하기와 매우 유사하지만 기능은 다음과 같다.
- CSR 로 수정할 게시글 가져오기
- user id 비교하여 페이지 권한 설정
CSR 로 수정할 게시글 가져오기
사실 SSR 로 가져오고 싶었지만 React Quill 로 인해 가져오지 못해 CSR 를 사용했다. 로컬에서는 잘 작동하지만 배포하면 작동하지 않는다.
user id 비교하여 페이지 권한 설정
원래 SSR 을 사용하여 게시물 작성자의 id 와 쿠키에 저장된 유저의 id 값을 가져와 비교하여 페이지가 로드되기 전에 바로 404 페이지로 보낼 계획이었으나🥹 React Quill 이슈로 CSR 로 변경하면서 위에 내용을 원하는 대로 구현할 수 없었다. 그래서 대신 게시물 작성자의 id 와 Zustand 로 상태 관리 하던 user 의 id 값을 비교하여 다르거나, 비회원인 경우 404 페이지로 리다이렉트 시켰다.
사실 좀 아쉽다 CSR 을 사용했기 때문에 해당 페이지가 잠깐 보였다가 404 페이지로 리다이렉트 되어 SSR 로 구현하고 싶었는데.. 다음에 기회가 되면 다시 구현해보고 싶다.
부가 기능 구현
404, 500 에러 페이지
자유게시판 페이지를 만들면서 404, 500 에러 페이지에도 커스텀이 필요한 거 같아 직접 디자인을 구상하여 만들게 되었다.
나중에 다른 팀원분이 Lottie 이미지를 다른 예쁜 걸로 바꿔주시고, 하늘에서 클로버가 떨어지는 것을 바닐라 JS 로 구현해 주셔서 해당 페이지가 더 예뻐졌다.
메타태그
SEO 향상을 위해 메타태그로 중요한 작업인데 이에 대한 내용은 https://jjang-j.tistory.com/137 에서 확인할 수 있다.
애니메이션 적용
내가 만든 페이지, 컴포넌트 이외에도 Button 컴포넌트를 클릭했을 때 애니메이션을 적용하고, 페이지를 전환했을 때에도 애니메이션을 적용했다. 페이지 애니메이션에 관한 내용은 https://jjang-j.tistory.com/128 에서 확인할 수 있다.
KPT 회고
팀 KPT 회고
👏 Keep
- 서로를 배려하여 트러블 없이 즐겁게 프로젝트를 완수했습니다.
- 매일 데일리 스크럼을 통해 각자의 작업 상황을 파악할 수 있었습니다.
- 리뷰 속도가 빠르며, 팀원들끼리 아낌없이 칭찬하고 부족한 부분에 대해서는 꼼꼼하게 피드백하였습니다.
- 본인이 맡은 일에 대해 책임을 가지고 마감 기한보다 빠르게 작업을 끝냈습니다.
- 사용자 경험 측면을 고려하고, 기능 및 성능 개선과 리팩토링 등 부가 요소에도 신경을 쓰며 작업했습니다.
😢 Problem
- 마감 기한이 다가올수록 본인이 구현해야 할 작업에 집중하다 보니 코드 리뷰가 늦어졌습니다.
- 다들 개발을 쉬지 않고 너무 열심히 하다 보니 컨디션이 좋지 않을 때가 있었습니다.
- 이슈 트래킹과 일정 관리 도구를 활용하지 않아 프로젝트의 전체 진행 상황 파악이 어려웠습니다.
- 디스코드에 잡담이 많아 중요한 메시지를 놓치거나 찾기 어려웠습니다.
💪 Try
- 시간 분배에 신경을 쓰며 코드 리뷰에 시간을 투자해 보겠습니다.
- 건강을 잘 챙기고 쉴 때는 쉬면서 작업해 보겠습니다.
- 다음 프로젝트에서는 일정이 길기 때문에 노션, 깃허브 프로젝트, 지라 등 다양한 도구들을 사용하여 관리를 해보겠습니다.
- 잡담은 새로 스레드를 만들어 사용하거나 새로운 채널을 파서 소통해 보겠습니다.
개인 KPT 회고
👏 Keep
- 맡은 역할에 대해 기간 내에 모두 완수했습니다.
- 주어진 내용 이외에도 사용자 경험성 측면을 고려하여 개발하였습니다.
- 대부분 PR 리뷰를 다 남겨주고, 팀원이 잘한 부분이 고쳤으면 좋겠는 부분을 최대한 작성하기 위해 노력했습니다.
😢 Problem
- 프로젝트 마감 기한이 다가올수록 PR 리뷰 속도가 느렸습니다.
- 후반으로 갈수록 컨디션 관리를 잘 못했습니다.
💪 Try
- 내 작업도 중요하지만 다른 팀원의 원활한 개발 진행을 위해 리뷰를 보면 최대한 빨리 리뷰를 남겨보겠습니다.
- 건강을 잘 챙겨가면서 개발을 하겠습니다.
2주 동안 정말 수고했고 팀원들한테도 감사하다 말하고 싶다! 남은 기간도 파이팅~~~
'💜 후기 및 활동 > 프로젝트' 카테고리의 다른 글
[Codeit Resources] 코드잇 인턴 프로젝트 회고 (0) | 2024.11.20 |
---|---|
[우주윗미] 코드잇 스프린트 심화 프로젝트 회고 (8) | 2024.09.06 |
[내 마음 속 바다] 개발 동아리 DND 10기 프로젝트 회고 (0) | 2024.06.11 |
[Fandom-K] 코드잇 스프린트 기초 프로젝트 회고 (1) | 2024.06.10 |
FE 개발자가 되고 싶은 짱잼이
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!