Skip to content

Latest commit

 

History

History
379 lines (267 loc) · 26.7 KB

README.md

File metadata and controls

379 lines (267 loc) · 26.7 KB

header

프로젝트 개요

친구들과 함께 소소하게 일상을 나누는 커뮤니티 풀스택 개인 프로젝트

프로젝트 확인 해보기

배포 페이지로 이동하기

테스트 계정

프로젝트 대한 간략 내용

모든 항목은 프론트엔드에서의 컴포넌트 및 페이지 등의 구현에서 백엔드에서의 데이터 스키마, API 등의 구현을 진행했습니다.

제작 : 2023.7.17 ~ 고도화 및 리팩토링 진행 중

  • 반응형 디자인 적용
  • 인증 로직 구현 (회원 가입, 로그인)
  • 트윗에 대한 Creat / Read / Delete 구현
  • 코멘트에 대한 Create / Read / Delete 구현
  • 회원 정보에 대한 Read / Update 구현
  • 좋아요 기능 구현 및 낙관적 업데이트 적용

계속적으로 리팩토링 및 고도화 진행 중, Pull Request 확인 가능

상세 이력 (자세하게 보기)
  1. Feature #2 트윗추가 및 프로필 사진 변경시 이미지 등록 로직 변경 #3 : 2023.08.11, Image 등록 로직 리팩토링
  2. Feature #9 API mutate 로직 리팩토링 #10 : 2023.08.22 ~ 2023.11.12, 자체 커스텀훅 useMutation에서 swr이 제공하는 수동트리거 useSRWMutation으로 리팩토링
  3. Feature #13 toast message 추가 및 적용 #14 : 2023.11.13 ~ 2023.11.14
  4. 로그인 인증에 관련된 로직 변경 :
  5. 좋아요API 중복 요청 문제 해결 :
  6. 삭제 권한 대한 문지 해결 :
  7. 무한 스크롤 기능 구현 :
  8. 팔로잉 기능 구현 :
  9. 반응형 디자인 구현 :

추가 기능구현 :

프로젝트 프리뷰 및 상세 내용

기본 레이아웃 적용 (mobile / desktop)

구분 mobile desktop
피그마 image image
실제구현 image image

트윗 상세페이지에서는 세가지의 디자인을 확인할 수 있다.

mobile tablet desktop
image image image

트윗

1. 트윗 및 코멘트 작성

  • 등록시에 콜드 스타트로 인한 지연시간 동안 사용자의 이상행동을 방지하기위해 코멘트 업로드 폼을 비활성화 시킵니다.

2. 낙관적 업데이트

  • 트윗 리스트(메인페이지)와 상세페이지에서 좋아요 버튼을 사용할 수 있습니다.
  • 서버리스함수인 API route의 콜드스타트로 발생되는 지연 시간으로 떨어지는 사용자 경험을 보완하고자, 모든 좋아요 버튼 및 댓글삭제 기능에 낙관적 업데이트를 적용했습니다. 이를통해, 사용자에게 즉각적인 반응을 줍니다.
  • 낙관적 업데이트를 적용된 부분에서 에러 날경우 롤백이 적용됩니다.

‘좋아요’ 버튼 중복 요청 문제 해결

3. 트윗 리스트 무한 스크롤 : Feature #32 트윗 리스트를 무한 스크롤 구현

  • 트윗 리스트(메인페이지)에서 전체 데이터를 일괄적으로 불러오는 대신, 필요에 따라 추가 데이터를 요청함으로써 성능 부담을 줄였습니다.
    • 트윗 리스트 API 에서 페이지네이션을 구현하여 클라이언트의 요청에 따라 필요한 데이터만 반환합니다.
  • 클라이언트에서는 무한 스크롤을 구현하기위해 마지막 트윗인지를 확인하고 다음 데이터를 요청하기위해 Intersection Observer API를 사용했습니다.
    • 브라우저 리플로우 문제를 방지하고자 스크롤 이벤트가 아닌 Intersection Observer API를 채택했습니다.
  • 추가 데이터를 불러오는 대기 시간 동안의 사용자 경험을 개선하기위해 로딩 스피너의 활용하였습니다.

3.1) 사용자들의 전체 트윗을 불러오는 '전체보기'와 로그인한 사용자가 팔로잉한 사용자들의 트윗을 볼 수 잇는 '팔로잉' 섹션이 존재 :

Feature #55 팔로우한 사용자들의 게시글을 모아 볼 수 있는 기능 #62

  • 로그인 한 사용자가 팔로잉하고 있는 사용자가 작성한 트윗을 모아 '팔로잉' TweetFeed에서 확인할 수 있습니다.
  • '전체보기'와 '팔로잉'은 공통의 TweetFeed 컴포넌트를 공유합니다.
전체보기 팔로잉

4. 스크롤 탑 버튼 구현 :

Feature #53 무한스크롤로 구현된 트윗리스트에 사용할 탑버튼 구현 Feature #63 레이아웃 컴포넌트 리팩토링 및 Nested Layout, Scroll to top 생성 #64

무한 스크롤링으로 구현되어있는 트윗 리스트에서 스크롤 탑 버튼을 사용하여 트윗리스트 상단으로 이동할 수 있습니다.

  • 브라우저 전체 페이지를 기준으로 스크롤이 생성된 것이 아닌, 프로젝트의 레이아웃 컴포넌트 기준으로 스크롤이 생성되기 때문에 useRefuseImperativeHandle를 사용하여 스크롤 탑 버튼을 구현하였습니다.
  • 레이아웃 컴포넌트에서 useImperativeHandle을 사용하여, 레이아웃 컴포넌트의 래퍼 컴포넌트의 refscrollToTop 메서드를 생성하였습니다.
    useImperativeHandle(
      ref,
      () => ({
        scrollToTop: () => {
          containerRef.current?.scrollTo({ behavior: 'smooth', top: 0 });
        },
      }),
      []
    );
  • ScrollTopButton 컴포넌트를 클릭할 경우 nestedLayoutRef.current?.scrollToTop()로 메서드를 실행 시켰습니다.

top button

5. 다른 페이지로 라우팅 할 경우, (무한)스크롤이 구현되어야하는 리스트를 감싸는 레이아웃 컴포넌트(Nested Layout)의 스크롤을 최상단 초기화 시킨다.

아래의 순서로 구현방식을 변경했습니다.

Nested Layout의 컴포넌트의 네비게이션을 클릭할 경우 스크롤 값을 초기화 한다.

버튼을 통해서 Nested Layout 컴포넌트의 스크롤 값을 초기화 시키는 것이 아닌, 해당 컴포너트가 언마운트 될때마다 최상단으로 초기화를 시킨다. 하지만, 컴포넌트가unmount 될 때와 새로 렌더링 될때 간극이 존재하여 최상단으로 초기화된 상태로 렌더되지 않기 때문에 useLayoutEffect 를 사용해서 페인팅 작업 전에 스크롤을 최상단으로 끌어올린다.

useLayoutEffect 를 사용하여 페인팅 이전에 스크롤을 최상단으로 올리는 방식에서 pathname 에 따라 고유한 키값을 주는 방식으로 변경한다. 리액트 바깥에서 제어하는 useLayoutEffect를 쓰는 것보다, key 값을 사용하여 리액트 라이프 사이클 내부에서 관리한다.

  • 같은 컴포넌트를 공유하는 TweetFeed와 FollowList는 스크롤 위치를 공유하게 됩니다. 스크롤 위치를 공유 하지않고 섹션 및 pathname을 바꿀 때 마다 컴포넌트 최상단으로 올리기 위해 레이아웃 컴포넌트에 pathname을 key값을 넣어 주었습니다.

    <div key={router.pathname} ref={containerRef}>
      {children}
    </div>

    nested layout

6. 내용

  • 개인정보보호를 위하여 이메일을 masking 처리 하였습니다.
  • 트윗과 코멘트 작성시간은 ‘분, 시간, 일, 주’ 단위로 기재 됩니다.

7. 삭제 기능

  • 작성자에게만 삭제 버튼을 렌더함하며, 트윗과 코멘트는 작성자만 삭제 할 수 있습니다.

삭제 권한 대한 문제 해결 - Feature #24 삭제 권한 취약점 문제 해결 #25

  • 작성자가 아닌 다른 사용자도 api 를직접 호출하면 트윗과 코멘트를 삭제할 수 있는 문제점 인식
    • 작성자와 로그인한 사용자가 동일하지 않을 경우 권한 없음의 에러를 반환합니다.
  • 외부에서 api 를 조작할 수 있는 문제점
    • 세션에 로그인한 유저 정보가 존재하지 않는다면 api를 부를 수 없습니다.

팔로우

1. 팔로우. 언팔로우 기능 : Feature #47 팔로잉 기능 추가

사용자는 서로 팔로잉 할 수 있습니다. 팔로잉 할수 있는 방법 아래와 같이 총 세가지 입니다.

트윗 작성자를 팔로우를 하고 있지않을 경우 팔로우 버튼이 보이며, 팔로우 하고있다면 버튼이 보이지 않습니다.

  • 트윗에서 팔로우 버튼을 누르면 낙관적 업데이트를 진행하여 버튼을 사라지게 합니다.
  • 이는, 트윗에서 현재 좋아요 버튼, 댓글 삭제, 코멘트 갯수가 낙관적 업데이트로 적용 되어있으므로 동일하게 api 호출을 감소시키면서,사용자에게 지연 시간없이 즉각적으로 UI 변경을 보여줍니다.

언팔로우 하기는 해당 사용자의 프로필 페이지에서만 가능하게 구현했습니다.

트윗리스트(메인페이지) 트윗상세페이지 팔로잉 할 사용자의 프로필 페이지
main detail tweet profile

프로필 페이지에서 팔로워와 팔로잉을 누르면 팔로워와 팔로잉 리스트를 렌더하는 페이지로 라우팅 됩니다.

  • 프로필 페이지에서 팔로잉과 팔로우에 hover 할 경우 인터렉션이 일어납니다.
  • 팔로잉, 팔로워 페이지에서 렌더되는 사용자들을 클릭할 경우 해당 사용자의 프로필로 라우팅 됩니다.
프로필 페이지 팔로잉 페이지 팔로워 페이지
profile2 following followers

트윗에서 모든 좋아요 버튼은 낙관적 업데이트로 구현되어있습니다.

트윗의 좋아요 n개 를 누르게되면 해당 트윗을 좋아요한 사람의 리스트 모달을 띄워 줍니다.

  • 모달은 리액트 포탈을 사용하여 다양한 위치에서 스타일링 제약 없이 사용하게 끔 생성하였습니다.
  • 모달은 닫기 버튼 뿐만 아니라 오버레이를 클릭하여도 닫을 수 있습니다.

좋아요한 사람 리스트 모달에 렌더되는 사용자들을 클릭할 경우 해당 사용자의 프로필로 라우팅 됩니다.

  • 만약 일정한 모달 크기에 비해 리스트에 존재하는 사용자가 많다면 스크롤를 생성합니다.

liked users

인증

로그인 유저인 경우일 때에만 해당 서비스에 접근이 가능합니다.

비로그인 유저일 경우 로그인 페이지로 리다이렉트 합니다. (비로그인이 접근할 수 있는 페이지 : 로그인페이지, 회원가입 페이지)

  • 쿠키를 확인하여 인증상태를 구별하고 그에 따라 라우팅을 조정합니다.
  • Next.js의 middleware를 사용하여 라우팅 요청마다 서버 사이드에서 세션의 로그인 사용자 유무를 확인하고 라우팅을 조정합니다.

middleware 및 쿠키옵션 설정 변경

초기 설계에서는 미들웨어가 쿠키의 키값(‘dam-witter’) 존재 여부로 로그인 상태를 판별했습니다. 그러나, 이 방식은 외부에서 임의의 쿠키의 키 값을 주입하는 경우에도 로그인 상태로 인식될 위험이 있었습니다. 이를 해결하기 위해 로그인 시 세션에 저장된 ‘user’ 값의 유무로 로그인 상태를 판별하는 방식으로 변경했습니다.

이 변경사항은 “Feature #17 middleware 설정 변경” 풀리퀘스트에 반영되었습니다.

또한, 추가적인 보안 강화를 위해 쿠키 옵션을 변하였으며 “feat: 쿠키옵션 설정 변경” 풀리퀘스트에 반영되었습니다.

로그인

  • 로그인이 실패할 경우 (이메일 또는 비밀번호가 올바르지 않은 경우 또는 존재하지 않을 이메일 경우) 에러 토스트메시지를 띄워줍니다.
  • 로그인 입력에 대한 유효 검증 메시지를 텍스트 메시지로 알려줍니다.

회원가입

  • 회원가입에 실패할 경우 (동일한 이메일이 있을 경우) 에러 토스트메시지를 띄워줍니다.
  • 회원가입 입력에 대한 유효 검증 메시지를 텍스트 메시지로 알려줍니다.
    • 이메일, 비밀번호, 비밀번호 확인(비밀번호와 동일한 비밀번호 인지 검증합니다.)
로그인 중 잘못된 입력시, 텍스트 및 토스트 메시지로 알람 회원가입 중 에러가 발생시, 텍스트 및 토스트 메시지로 알람 회원가입 중 서버 에러 응답을 토스트 메시지로 알람
login error create account error 1 create account error 2
  • 좋아요 및 코멘트를 조작하면 두 항목의 갯수에도 낙관적 업데이트가 동일하게 적용되어있습니다.
    • 코멘트 삭제시, 삭제하는 코멘트를 제거하고 코멘트 갯수를 감소시키는 낙관적 업데이트가 적용되어있습니다.

이미지 등록

사용자 경험을 위해서 작성자가 트윗을 등록할 때, 이미지 등록 과정을 비동기적으로 처리하여 트윗 등록 시간을 감소시켰습니다. (이는, 이미지를 등록하는 모든 곳에 적용됩니다.)

  • 사용자가 이미지를 등록하면 바로 cloudflare Image에 이미지를 등록 과정이 진행됩니다.
  • 사용자가 등록한 이미지를 프리뷰 보여줍니다. 만약, 사용자가 다른 이미지를 선택하거나 이미지를 등록하고 싶지않을 때 프리뷰 이미지를 제거하고 등록을 취소하는 사진등록취소버튼을 만들어 두었습니다. 회원정보수정에서 사진등록취소 버튼을 누를 경우, 이전에 설정되어있는 프로필 사진으로 자동 교체**됩니다.
트윗 작성 페이지 회원 정보 수정 페이지 (이미지 등록 전) 회원 정보 수정 페이지 (이미지 등록 후)
upload tweet edit profile edit-profile2

프로필

  • 최초 가입시에 프로필 사진은 제공하지 않습니다.
    • 사용자가 등록하지 않을 경우, 기본 프로필 이미지를 렌더합니다.
  • 최초 가입시에 자기소개의 기본 값이 적용 됩니다.
    • (기본 자기소개 값 : 안녕하세요. ${name}입니다.,)

기타 구현 사항

Prisma 데이터 스키마, API response 타입, 클라이언트 swr 타입 연계 및 관리

API 데이터 값의 일치를 지향하고자, API 반환 값의 타입과 클라이언트에서 응답받고 사용하는 데이터 반환 값을 통일 시켰습니다.

Prisma에서는 스키마 파일을 바탕으로 Prisma Client를 생성하면, Prisma는 자동으로 해당 데이터베이스 스키마에 대응하는 TypeScript 타입을 생성합니다. 이를 타입을 이용하여 Next.js에서 지원하는 API Response 타입인 NextApiResponse<API 반환 타입> 와 결합하여 사용하였습니다. 또한, 클라이언트 단에서 useSWR로 서버데이터 페칭 및 관리를 같은 타입으로 사용하였습니다.

아래는 프로필 API response 타입과 클라이언트에서 swr로 해당 API를 같은 타입을 사용해 부르고 관리하는 코드입니다.

API swr
NextApiResponse<ResponseType<ProfileResponse>> useSWR<ResponseType<ProfileResponse>>()

클라이언트에서 API 호출시, END POINT를 객체 상수화 하여 관리

아래와 같이 객체 상수화 하여 사용하는 곳에서 어느 endpoint 인지 명확하게 알 수 있으며, 휴먼 에러로 인한 잘못된 API 요청을 줄였습니다.

export const END_POINTS = {
  COMMENT: (tweetId: string, commentId: string) => `/api/tweets/${tweetId}/comments/${commentId}`,
  COMMENTS: (tweetId: string) => `/api/tweets/${tweetId}/comments`,
  CREATE_ACCOUNT: '/api/users/create-account',
  FILES: '/api/files',
  FOLLOW: `/api/follows`,
  FOLLOWS: (userId: string) => `/api/follows/${userId}`,
  LIKE: (tweetId: string) => `/api/tweets/${tweetId}/like`,
  LOGIN: '/api/users/log-in',
  LOGOUT: '/api/users/log-out',
  MY_PROFILE: '/api/users/profile',
  MY_PROFILE_EDIT: '/api/users/profile/edit',
  PROFILE: (userId: string) => `/api/users/profile/${userId}`,
  TWEET: (tweetId: string) => `/api/tweets/${tweetId}`,
  TWEETS: '/api/tweets',
};
  • 클라이언트에서 route path 또한 같은 방식으로 관리하여, 명확한 route path를 명시합니다.

제어 컴포넌트 폼관리 커스텀 훅 생성

  • 폼 처리를 위해 useForm이라는 커스텀 훅과 유효성 검사기를 결합해 사용했습니다.
  • 처음에는 React의 배치 업데이트 기능을 충분히 고려하지 못해, 사용자의 입력 값과 검증된 값 간에 동기화 이슈가 발생했습니다. 해당 이슈는 사용자의 입력 값을 직접 검증하는 대신, 입력 상태를 복사본을 만들어 검증함으로써 해결했습니다.

프로젝트 ERD

erd

프로젝트 실행 방법

  1. Clone the repo
$ git clone https://github.com/j2h30728/dam-witter.git
  1. Install NPM packages
$ npm install
  1. 환경 변수 설정
//.env 생성 후, 아래의 설정값 추가
COOKIE_PASSWORD=/*최소 32 이상 랜덤 글자 설정*/
CLOUDFLARE_API_TOKEN=/* cloudflare API token 설정 */
CLOUDFLARE_ACCOUNT_ID=/* cloudflare Account Id 설정 */
DATABASE_URL=/* 데이터베이스 url 설정 */
  1. Getting Started
$ npm run dev