제로힙(Zerohip) 1.1

제로힙(Zerohip) 1.1

Author
Team Projects
Published
July 1, 2023
Description
가계부와 일기장, SNS 서비스가 함께 제공되는 절약 유도 가계부형 SNS입니다.
Features
✔︎ jwt 기반 로그인 / 로그아웃 구현 ✔︎ SEO 최적화 ✔︎ 프로젝트 팀장
Tags
React.js
Next.js13
PagesRouter
Emotion
TanstackQuery
Axios
Redux/toolkit
Public
ServerOff

💰 프로젝트명 : 제로힙(Zerohip) 1.1.0

🔥 프로젝트 소개(기획의도 및 개요):

제로힙은 `당신의 과소비가 0에 수렴할 때까지`를 슬로건으로 가계부와 일기장, SNS 서비스가 함께 제공되는 절약 유도 가계부형 SNS입니다. 특히 과시를 조장하는 기존의 SNS에 대항하기 위하여 절약을 전시하는 SNS 서비스를 제공하여 소비의 가치를 함께 나누고자 합니다.

🔗 링크

 

⚒ 기술 스택


notion image
 
 

Front

  • TypeScript
  • React
  • Next.js
  • Redux Toolkit
  • Emotion
  • TanStack Query
 

Back

  • Java
  • Spring Boot
  • Spring Security
  • MySQL
  • AWS
  • Nginx
  • SSL
 

Deploy

  • FE
    • Vercel
  • BE
    • S3
    • EC2
    • Route 53
 
 
 

COMMON

  • Git
  • GitHub
  • Figma
  • Prettier
  • ESLint
 

📜 문서

[기획] 사용자 요구사항 정의서
 
 
📜 [BE] DB Schema

notion image
 
🎨 [FE] 화면 정의서 및 디자인 목업

 

👍🏼 기여


역할 : 팀장 / 프론트엔드

구현 기능 등

프론트엔드 배포, 로그인, 회원 CRUD, 반응형, SEO 최적화와 전반적인 문서 정리
 
  1. 배포
      • Vercel를 이용한 프론트엔드 배포
  1. 로그인 및 로그아웃
      • 일반 로그인, 로그아웃 기능
      • 네이버, 구글, 카카오 OAuth2
      • AccessToken, RefreshToken을 이용한 보안 설정
  1. 회원 CRUD
    1. 회원 가입
        • 아이디, 닉네임, 비밀번호 등에서 유효성 검증 검사를 통한 회원 가입 로직 작성
    2. 마이페이지:
        • 유저정보조회(닉네임, 프로필 사진, 내가 쓴 글)
        • 무한 스크롤
    3. 회원정보수정
        • 이미지 또는 오픈 API를 통한 두 가지 방식의 프로필 사진 수정 기능
            1. 이미지 : 유저의 컴퓨터에서 사용자 정의 이미지를 선택하여 원하는 이미지로 프로필 사진을 지정
            1. 오픈 API : BoringAvatar 오픈 API를 통해 랜덤 아바타 뽑기로 사이트에서 제공하는 아바타로 프로필 사진을 지정
        • 닉네임, 비밀번호 수정
    4. 회원탈퇴
  1. SEO 최적화
      • OG를 포함한 메타 데이터 컴포넌트 생성
  1. 문서 정리
      • 노션 : 기획서, 컨벤션, 회의록, 회고 등 기록
      • Google Sheet : 사용자 정의서, API 명세서, 개발자 테스트 등 엑셀 시트를 활용
  1. 반응형
      • 전담한 페이지에서의 전반적인 반응형 구현
 
  • 유튜브 프로젝트 소개 영상 : 본인 파트 00:00~03:36(초)
 
Video preview
 

⌨️ 개발 내용


로그인 검증 함수 미들웨어 사용

고차 컴포넌트(HOC)를 활용한 로그인 유무 검증을 진행했습니다.
  • 메인 컴포넌트를 mount하기 전에, AccessToken과 RefreshToken이 있는지 검증하는 컴포넌트를 고차 컴포넌트로 분리했습니다.
    • 해당 컴포넌트에서 로그인 검증이 완료되면 로그인 서비스가 필요한 메인 컴포넌트를 반환합니다.
    • 해당 컴포넌트에서 로그인 검증이 실패되면 안내 메시지와 함께 로그인 서비스가 필요하지 않은 홈 화면으로 이동합니다.
 
예시) 마이 페이지에 로그인 검증 컴포넌트(WithAuth)
import withAuth from '../../../components/WithAuth'; // 1. 해당 함수를 import해서 . . function MyPage() { . . export default withAuth(MyPage); // 2. MyPage라는 컴포넌트를 감싸주면 됩니다.
 

재사용 가능한 input, checkout 박스 생성

React Hook Form을 쓰지 않은 이유는 라이브러리 없이 기능을 자체적으로 구현해보고 싶었기 때문입니다.
onChange 함수를 내장하고, 컴포넌트 등을 리턴하는 input hook을 만들고, 필요한 곳에 사용했습니다.
 
// 1. 쓰고자 하는 컴포넌트에 해당 컴포넌트를 import 합니다. import useInput, { useCheckboxInput } from '../../../hooks/useComponents'; // 2-1. 인풋 custom hook의 경우 const [IdInput, loginId, setLoginId] = useInput('text', '아이디', 'loginId'); // 2-2. 체크박스 custom hook의 경우 const [Checkbox, isChecked, setIsChecked] = useCheckboxInput('checkbox','policy'); // 3. 필요한 곳에 {IdInput} 또는 {Checkbox}을 넣어주면 컴포넌트가 들어갑니다. <S.Policy> {Checkbox} <S.RadioBtnLabel htmlFor='policy'>전체동의</S.RadioBtnLabel> <S.PolicyGuide htmlFor='policy'> {SIGN_UP_MESSAGES.POLICY_GUIDE} </S.PolicyGuide> </S.Policy>
 

🖥 기능별 구현 결과


로그인 및 로그아웃

로그인 - 서버 에러 메시지, RefreshToken 쿠키 저장 (gif) 👈🏼  클릭!
notion image
 

회원 CRUD

회원가입 - 유효성 검증 (gif)  👈🏼  클릭!
notion image
마이페이지 - 웹접근성 개선 (gif) 👈🏼  클릭!
notion image
마이페이지 - 무한스크롤 (gif) 👈🏼  클릭!
notion image
회원수정 - 프로필 사진 등록 (gif) 👈🏼  클릭!
notion image
회원탈퇴 및 탈퇴 성공 페이지 이동 (gif) 👈🏼  클릭!
notion image
 

SEO 최적화

메타 데이터 태그 및 카카오톡 공유 (이미지) 👈🏼  클릭!
notion image
notion image
 

반응형

시연 화면 (영상) 👈🏼  클릭!
notion image
 
 

🧐 배움


 
[SSR로 동적 메타 태그 생성 외 SEO 최적화]
동적 메타 태그로 SEO에 최적화시켜야 하는 파트는 ‘SNS 게시글'로 제 파트는 아니었지만 제가
SEO 최적화 파트를 담당하고 있었기 때문에 userId를 params로 하여 동적으로 특정 유저의 페이지를 렌더링하는 페이지에서 SSR(Server Side Rendering)을 활용한 동적 메타 태그 생성을 시도해보았습니다.
notion image
Next.js를 선택한 SSR 지원에 대해서 막연히 SEO를 위해 필요한 방식이라고 생각만 해왔고, Next.js가 어떤 방식으로 SSR을 구현하는가? 에 대해서 실제로 고민할 기회가 없었기 때문에 Next.js의 동작 방식부터 SSR의 원리를 근본적으로 알 수 있게 된 계기였습니다.
우선 프로젝트에서는 Next.js의 app routing 방식이 아닌, pages routing 방식을 쓰고 있었고 해당 방식에서는 모든 컴포넌트가 최상위 컴포넌트인 _app.tsx를 통해 상위부터 하위까지 차례로 렌더링되는 구조라는 것을 알게 되었습니다. 즉, _app.tsx의 <Component {...pageProps}>는 먼저 서버에서 실행되는 getServerSideProps 함수 안에서 미리 데이터를 요청하고 받아와서 최상위 컴포넌트인 _app에서 아래로 해당 데이터들을 pageProps 형식으로 건네주기 때문에 SSR가 가능했던 것입니다.
특히, 데이터를 받아오는 라이브러리로 TanStack Query (구 React Query)를 사용하고 있었기 때문에 기본적인 fetch나 axios가 아닌 TanStack Query가 SSR을 지원해주는 방식을 사용하고자 했습니다. 기본 구현은 우선적으로 라이브러리의 공식문서를 참조했고, 더 나아가 동적 라우팅으로 쓰인 userId params를 참조하는 방법과 타입스크립트 같은 경우에는 stackoverflow와 여타 많은 구글링을 통해 최종적으로 SSR 방식을 구현할 수 있었습니다.
[오버 커뮤니케이션의 중요성]
프로젝트 집중 기간 내내 팀원들의 참여가 워낙 활발하여, 24시간 채팅이 진행되곤 했습니다. 그래서 정기적인 전체 회의는 생략하고 필요할 때만 회의를 진행하는 것이 어떨까? 라는 의견이 있었고, 저 역시 제한적인 리소스를 사용하는 데 있어서 효율적이라는 생각이 들었습니다. 하지만 결과적으로 특별한 이슈가 없어도 정기 회의를 하면서 스몰톡으로 사소하게 나올 수 있는 이야기들을 하지 못했고 그 당시 간단히 잡을 수 있었던 문제들을 감지하지 못했던 문제가 있다는 것을 깨닫게 되었습니다.
백엔드 부분에서 브랜치를 merge할 때마다 충돌 이슈가 있었는데 그 뒤로 별 이야기가 나오지 않아서 해결되었겠거니 생각하고 지나갔던 적이 있습니다. 하지만 나중에 알고보니 문제는 계속 발생하고 있던 중이었습니다. 정기회의에서 가볍게 이슈에 대해 이야기를 했다면 진작 모든 팀원들이 해결을 위해 의견을 내고 해결방법을 찾아봤을 텐데 그 과정을 생략하였기 때문에 오히려 리소스를 의미없이 잡아먹고 있다는 것을 깨달았습니다. 그 뒤, 다시 정기회의가 진행되었고 서로의 현재 진행 중인 사소한 이슈나 어려움, 그리고 구현한 기능에 대해서 이야기를 나누는 시간을 가지게 되었습니다.
일련의 사건을 통해서 커뮤니케이션은 명확하게 전달되어야 한다면 더블체크 등으로 오버 커뮤니케이션이 된다고 해도 다른 사람과의 원활한 협업을 위해서는 이러한 과정이 정말 필수적이라는 것을 경험적으로 가슴 깊이 새길 수 있었습니다. 기술적 소통을 할 때에도 다른 직무의 사람이 알아들을 수 있게, 또 최대한 의도한 바를 정확하게 전달하기 위해 간결하고 쉬운 단어와 예시를 들어 설명하고자 노력을 했습니다.
[프론트엔드 생산성 높이기 : MSW, Next.js API Routes]
처음 백엔드와의 협업 프로젝트를 진행하면서 힘들었던 점은 백엔드에서 개발 시일이 약속보다 늦어져서 프론트엔드의 일정 역시 영향을 받게 되는 점이었습니다. 하지만 이러한 문제들은 서버를 모킹하거나 서버를 지원해주는 프레임워크의 이용으로 해결할 수 있다는 것을 알게 되었습니다.
첫 번째 프로젝트 때에는 MSW(Mock Service Worker)로 백엔드 API를 모킹하여 미리 데이터를 받아와서 화면에 그려보고, 무한 스크롤 등과 같이 복잡한 서비스 역시 구현할 수 있었기 때문에 나중에 백엔드에서 API가 완성이 되면 작성한 코드를 그대로 백엔드와 연결하여 개발 시간을 단축할 수 있었습니다. 또한 두 번째 프로젝트에서도 역시 Next.js의 API Routes(Pages Router)를 사용하여 생산적으로 프론트엔드 개발을 정체없이 추진할 수 있었습니다.
성공이나 실패를 가정하고 response에 특정 status code나 message를 넣어 실제 네트워크 요청을 가장할 수 있었기 때문에 성공/실패 메시지 출력이나 무한 스크롤 구현 등 요청에 대해 데이터가 어떻게 화면에 그려지는 가에 대해 실제 코드를 적용하여 확인할 수 있다는 큰 장점이 있었습니다.
(GitHub PR 링크↗︎: 마이페이지 Get 요청 예시 - Vercel preview URL↗︎)
[RefreshToken과 로컬테스트]
팀 내 컨벤션 중 하나는 ‘로컬 테스트를 거치지 않고 dev 브랜치로 PR을 보내지 않는다'였는데 로그인 같은 경우에는 로컬 환경이 실제 배포 환경과 꽤 다른 부분이 있어 어려움을 겪었던 적이 있었습니다. 백엔드에서는 코드가 잘못된 게 없다고 하셨고, 또 실제로 POSTMAN에서는 기능이 잘 작동 되었기 때문에 문제를 해결하는 데 있어서 원인 파악이 되지 않아 다소 헤맸지만, 결국 로컬에서는 쿠키 설정을 따로 해줘야 한다는 사실을 알게 되었습니다.
문제가 일어났던 원인은 Secure=false, SameSite= “None”이라는 설정이 브라우저에서 차단이 되었기 때문인데, 이 두 속성은 서로 상충되는 속성으로 동시에 설정할 수 없었기 때문이었습니다. 알고보니 크롬 브라우저를 기준으로 http 환경, 즉 로컬 테스트를 위해서 Secure 속성을 true로 설정해도 가능했고, SameSite는 그대로 “None”으로 설정하여 그렇게 개발 환경에서 테스트를 이어서 진행할 수 있었습니다.
문제를 해결할 수 있었던 것은 기존에 쿠키와 관련하여 알고 있었던 지식이 있었고 자바 코드를 하나하나 뜯어가며 시도를 해본 덕분이었습니다. 이 대목에서 결국 백엔드 및 네트워크 쪽 지식이 있어야 함을 알게 되었고, 한 멘토님 께서 어떤 웹 개발자든 결국에는 풀스택을 향해 간다-라는 말씀을 지나가듯이 해 주었는데 그게 무슨 의미인지, 그리고 네트워크나 백엔드 관련하여 최소한의 공부를 꾸준히 해야 한다는 것을 알 수 있었습니다. (관련 3주차 회고↗︎)
notion image
 

👣 향후 보완점 및 업데이트 계획


🐱
모든 팀원의 자율적인 합의 하에 제로힙 프로젝트를 계속 진행하며, 정의서에 있는 내용을 100% 구현하기로 했습니다.
 

보완점

  • 전반적인 리팩토링 및 성능 최적화를 통한 사용자 경험 향상
    • 이미지 최적화
    • Reflow / Repaint 최소화
 

제로힙(Zerohip) v 2.0.0 업데이트 계획

  • 실시간 채팅 및 알람 기능 추가
    • WebSocket을 활용한 양방향 프로토콜 구축
  • 검색 기능 추가
    • 검색 결과에 검색한 단어를 UI적으로 표시
  • 임시저장 기능 추가
    • throttle을 활용하여 일정 시간 동안 다른 입력이 없을 경우 데이터를 임시로 저장하는 기능 구현