Skip to content

nsong113/Nine-Cloud-Front

 
 

Repository files navigation

🏡 Nine Cloud - FE

award/

나인 클라우드 브로셔

나인 클라우드은 AI 감정 솔루션 그림일기 서비스 입니다.
하루의 일과를 그림 과 글로 정리하여 그날의 감정 이모티콘과 함께 기록해보세요.


☁️ Nine Cloud

cloud nine (행복의 절정)이라는 속담에서 착안했습니다. 행복한 감정을 기록 (cloud), 9가지의 감정 표현 이라는 점에서 저희 서비스에 어울리는 속담 같아 나인클 라우드라고 설정해 보았습니다.


구분 링크 구분 링크
클라우드나인 아이콘 Nine Cloud 노션 아이콘 브로슈어
리액트 아이콘 FE Github 노션 아이콘 팀 SA
노드js 아이콘 BE Github 피그마 아이콘 Figma

👩🏻‍💻👨🏻‍💻 FrontEnd 개발

주철민 송지우 한덕용
cheolminJOO nsong113 HyperQuanx

💻 역할 담당

주철민 - 달력 디자인 커스텀
- 일기 상세페이지, 수정페이지
- 마이페이지 기능 구현
- 전반적인 alert 창 관리 (sweetAlert2)
- 댓글, 좋아요 기능 구현
- 페이지 전환 애니메이션 (framer-motion)
- 배포 관리(vercel), 이미지 관리(AWS S3)
cheolmin.joo@gamil.com
송지우 - 글 작성 페이지 (슬라이드 바, react-quill)
- 그림그리기 기능 (canvas)
- gpt open api 연결 및 prompt 작성
- 커뮤니티 페이지 (infinite-scroll)
- 개인 글 보기 피드 (infinite-scroll)
- 모달 (react-portal)
nsong113@gmail.com
한덕용 - Onboarding 페이지
- Sign in / Sign Up 페이지
- OAuth 2.0 Social Login (Kakao, Google, Naver)
- Kakao 공유하기
- Socket.io를 사용한 실시간 채팅
- Axios intercepter
qbixroqkfwk@gmail.com

🎈 주요기능 소개

소셜 로그인 달력 페이지 그림 그리기
소셜 로그인 메인 일기 추가하기

AI open api 무한스크롤 채팅방
AI 커뮤니티 채팅방

🗂️ 서비스 아키텍처

서비스아키텍처


⛳️ FE 기술 선정


🌟 기술적 의사결정

TypeScript 타입스크립트는 정적 타이핑을 지원하며, 코드 작성 시 변수의 타입을 명시적으로 지정할 수 있습니다.
이는 컴파일러가 코드를 분석하여 타입 에러를 사전에 찾아내고 예방할 수 있게 해 주기 때문에 코드의 일관성을 유지할 수 있다는 장점이 있습니다. 실제 연구에 따르면 모든 JavaScript 버그의 15%가 사전에 TypeScript로 감지할 수 있다는 결과를 보고 채택했습니다.
Axios 직관적인 API를 제공하여 HTTP 요청을 손쉽게 처리할 수 있습니다.
또한 요청과 응답을 가로채는 인터셉터(interceptor)를 제공하여 요청 또는 응답을 수정하거나
에러를 처리하는 등의 다양한 기능을 제공 받을 수 있으며 사용이 간편하여 채택했습니다.
React Router React Router는 SPA(Single Page Application)에서 클라이언트 사이드 라우팅을 관리합니다.
페이지 리로드 없이 URL을 변경하고, 적절한 컴포넌트를 렌더링하여 사용자에게 매끄러운 페이지 전환 경험을 제공하기에 채택했습니다.
React Query 간편한 API 및 자동화된 캐싱, 재시도, 상태 관리를 재공하여 데이터 요청 및
관리를 간소화하며, UI와 상태를 효율적으로 동기화할 수 있어 선택했습니다.
Recoil Recoil은 Redux와 다르게 설정이 비교적 간단하며, 컴포넌트 간에 필요한 상태를 손쉽게 공유할 수 있습니다.
Redux와 다르게 직접 상태관리를 변경할 수 있어 여러 컴포넌트에서 같은 상태를 쓰거나 수정할 때 편리하여 채택했습니다.
Styled Components 각 컴포넌트에 고유한 스타일을 적용할 수 있게 해주어 전체 애플리케이션의 스타일링이 더욱 모듈화되고 관리하기 쉬워집니다.
또한 개발 효율성을 높여주며 동적 스타일링에 용이하여 채택했습니다.
Socket.io 웹 소켓(WebSocket)기반으로 동작하는 JS 라이브러리입니다.
양방향 통신을 가능하게 해서버와 클라이언트간에 데이터를 실시간으로 주고받을 수 있습니다. 특히 자동으로 브라우저의 종류에 맞춰 호환되는 기술을 자동으로 선택하여 실시간 통신이 가능하게 만들어 주기 때문에 채택했습니다.
Intersection observer 인피니티 스크롤 구현을 위해 사용했습니다.
스크롤 이벤트를 사용하면 매 스크롤마다 이벤트 핸들러가 호출되어 성능에 부담이 될 수 있는데,
이 API를 사용하면 비동기적으로 작동하며, 뷰포트와 대상 요소 간의 교차 여부를 비동기적으로 감지합니다.
스크롤 위치와 상관 없이 요소가 화면에 나타나거나 사라질 때 이를 감지할 수 있어 웹 페이지의 부하를 감소시킬 수 있습니다.
sweetAlert2 기존의 딱딱한 alert창 대신에, 애니메이션이 들어가고 부드러운 디자인의 alert 창을 띄우기 위해 사용했습니다
간단하게 다양한 커스텀을 alert창에 적용할 수 있어서 본 프로젝트에 적용했습니다.
browser-image-compression 이미지를 압축하여 웹 페이지의 성능을 향상시키고 사용자 경험을 최적화하기 위해 적용했습니다.
해당 라이브러리는 압축을 진행하며 상당한 품질을 유지하고 압축률을 조절할 수 있습니다.

🛠️ 트러블슈팅

달력 월 변경 시, 달력 데이터 표기 오류 에러 [철민] - 문제의 디테일
오늘이 2024년 1월 4일이라면 2023년의 모든 달력은 데이터가 다 나타나지 않고, 모든 달력이 4일, 즉 오늘 날짜까지만 데이터가 등장합니다.

- 해결을 위한 노력
조건부 렌더링을 어떻게 줘야 제가 원하는 대로 구현이 될까 손코딩으로 메모장에 여러가지 경우의 수를 적어놓고 정리한 다음, 하나하나 적용했습니다.
- 해결 방법
오늘 일,월,년도의 데이터와, 달력이 바뀔 때 일,월,년도의 데이터를 변수로 정리하고 이 변수들을 이전 달, 이번 달,이후 달들에 대해 구분을 할 수 있는 const에 대입시켜 만들고, 이들을 조건부 렌더링에 적용해 해결했습니다.
ReactQuill 보안 에러 [철민] - 문제의 디테일
ReactQuill은 글자를 입력하고 출력하면 태그가 같이 붙어서 출력되는 문제가 있습니다. 그래서 이를 막기 위해서 dangerouslySetInnerHTML으 세팅을 해야 태그는 빠지고 글자들만 출력됩니다. 근데 이렇게 되면 문제가 DOM에 HTML을 추가할 때 사이트간 스크립팅 공격(XSS)에 취약해집니다. 그래서 관리자가 아닌 악성 유저가 페이지에 악성 스크립트를 삽입하는 등 비정상적인 기능을 수행할 수 있는 보안 구멍이 생깁니다.
- 해결을 위한 노력
dangerouslySetInnerHTML을 쓰지 않고, 태그 없이 글자를 출력할 수 있는 방법이 없을까? 라고 구상을 해봤는데 딱히 보이지 않아, dangerouslySetInnerHTML은 쓰는데 보안을 강화할 방법을 찾아보려고 노력했습니다.
- 해결 방법
DOMPurify라는 HTML을 sanitize 해주는 라이브러리를 활용했습니다. 그래서 DOMPurifiy에 내부함수인 sanitize 안에 인자로 출력할 글자를 넣으면 XSS 공격을 막게끔 할 수 있었습니다.
이미지 압축 후 서버에 전달 할 때 '다음'을 두번 클릭해야 하는 이슈 [지우] - 문제의 디테일
이미지를 압축 후 서버에 전달하는 버튼을 두번 눌러야 전달이 되었습니다. 첫번째 클릭에서는 압축이 되고 두번째 클릭에서 전송이 되는 것을 확인했습니다.
- 해결을 위한 노력
버튼을 두개를 만들거나 유저한테 더블클릭 해달라고 메시지를 전달해야 하나 고민을 했습니다. 주로 유저에게 어떻게 이 것을 덜 불편감으로 다가오게 전달할까 고민을 했습니다. 하지만 직접적으로 유저에게 요청을 하는 것은 큰 불편감으로 초래한다는 생각이 들어 어떻게 하면 자동화 할까 고민을 시작하였습니다.

- 해결 방법
그림을 그릴 때 마우스를 때고 1000ms 있다가 자동으로 이미지 압축 함수가 실행되는 debounce를 하는 함수를 만들었습니다. 사용자가 다른 그림그리기 기능을 클릭하기 위해서 움직이는 속도가 평균 1초 미만이고 그림 그린 후 '다음'버튼을 누르기까지 1초 이상이 걸린 다는 데에서 착안했습니다. 결과로, '다음'버튼을 한번만 눌러도 압축이 진행된 파일과 함께 post요청을 보낼 수 있었습니다.
무한으로 api 요청이 가는 무한스크로 [지우] - 문제의 디테일
'무한정'스크롤이 되어 계속 통신 요청을 하고, 처음 요청을 하고 더 요청을 안하기도 하고, 혹은 Null로 요청을 하는 사태가 발생했습니다.

- 해결을 위한 노력
1. InfiniteScroll 라이브러리를 다운받고 useInfiniteQuey를 같이 사용해 보았습니다. 결과로는 무한 get 요청이 보내졌습니다.
2. InfiniteScoll을 사용하지 않고 useInfiniateQuery의 getNextPageParam부분을 수정해 보았습니다. 결과로는 바닥을 인식하지 못했는지 첫번째 로딩만 하고 두번째 페이지는 로딩을 하지 않았습니다.
3. api의 return 부분을 수정했습니다. return에서 data이외에도 nextPage와 isLast를 리턴했습니다. 그리고 이것을 getNextPageParam에 적용했습니다.

- 해결 방법
api부분에서 data이외에도 nextPage와 isLast를 리턴하여 useInfiniteQuery의 getNextPageParam부분에 적용했습니다. react-intersection-observer 라이브러리를 도입하였습니다. 관찰하는 객체 하나를 ref로 설정한 후 해당하느 객체가 화면에 보이면 inView를 true로 값을 바꿔줍니다. 저의 경우 가장 마지막 부분에 div를 만들고 color를 투명하게 주었습니다. useInfiniteQuery를 사용하였습니다. 특히 fetchNextPage메서드를 사용하여 inView가 true이고 hasNextPage가 true이면 fetchNextPage를 하도록 useEffect에 의존성을 추가하여 주었습니다.
토큰 재발급 관련 [덕용] - 문제 -
1. 서버에서 지정한 만료시간과 클라이언트에서 계산한 만료시간이 다름
2. 토큰 재발급 로직 작성 중에 모든 에러 사항에서 Token 재발급으로 들어가버리는 오류와 에러코드가 Server와 안 맞는 오류.
- 해결을 위한 노력 -
1. 처음에는 Server에서 empireTime(만료시간)이라는 값을 보내주도록 요청하였습니다. Server에서는 만료시간이 Unix time으로 설정되어 있다고 해서 Front에서 현재시간을 Unix time으로 바꿔주는 로직을 만들고 (만료시간 - 현재시간)으로 유효기간을 계산하고 setTimeout()을 사용해서 Front에서 시간을 재서 시간이 끝나면 자동적으로 재발급 요청을 하도록 로직을 생각해 보았습니다. 하지만 서버에서 지정한 만료시간과 클라이언트에서 계산한 만료시간이 다른 오류가 생겼고 이 뿐만 아니라 토큰 관리가 복잡해지고, 불필요한 서버요청이 계속 생기며 만료시간을 프론트에서 저장하면 보안에도 취약할 것 같아서 이 방법을 버리고 만료가 된 이후에 서버 요청을 한다면 자동적으로 토큰 재발급 URL로 접근할수 있도록 하기 위해 Axios intercepter를 사용했습니다.
2. 일단 실험을 위해 Token 만료시간을 극단적으로 짧게 설정해놓았습니다. Token이 만료된다면 대체적으로 서버에서 401번이나 419번 에러코드를 보내준다는 여러 레퍼런스들을 참고해서 Server와 에러코드를 맞춰보았습니다. 401번은 다른곳에서도 사용한다는 얘기를 듣고 Token 만료 에러 코드를 419번으로 통일하였습니다. 인터셉터 로직에는 419번 에러코드가 뜬다면 /token으로 이동되도록 설정해주고 헤더에 RefreshToken을 담아서 Server에 토큰 재발급을 요청하였습니다.
- 결과 -
토큰이 만료되면 419번 에러코드가 뜨고 자동으로 /token으로 넘어가서 RefreshToken을 대조한 후 새로운 AccessToken을 발급해줌.
Socket.io [덕용] - 문제 -

서버에서 지정해준 URL로 접근하지 못하는 현상.



서버에서 설정해준 URL로 접근하는 과정이 쉽지 않았습니다.



분명 엔드포인트를 /community/chat/socket.io로 설정해놓았는데 막상 개발자 도구를 보면 설정한 엔드포인트로 접근하지 못했습니다.

- 해결을 위한 노력 -


혹시 path 설정을 따로 해준다면 접근할 수 있을까 해서 기존에 하던 방식과 다르게 path 설정을 집어넣었습니다.

- 결과 -

결국 Server와 연결에 성공하였고 채팅 기능까지도 성공했습니다.

🛠️ 최적화

1. 번들 크기 최적화
react-icons는 종류별로 js파일을 하나씩 가지고 있어서, 하나의 아이콘을 실행하면 그 아이콘이 속한 js 파일이 실행돼서 관련 없는 불필요한 파일들도 함께 빌딩 돼 번들 크기가 커집니다. 그래서 @react-icons/all-files를 설치했고, 이 라이브러리는 아이콘마다 js 파일을 가지고 있어서 빌딩 시 필요한 파일만 실행 돼 번들 크기 최적화를 시킬 수 있었습니다.
2. React.lazy를 이용하여 라우팅 code splitting / webP확장자로 변경
- React.lazy는 React에서 코드 스플리팅을 구현하기 위한 기능 중 하나입니다. 코드 스플리팅은 애플리케이션을 더 작은 청크(chunk)로 나누어 각 청크가 필요할 때만 로드되도록 하는 기술입니다. 이를 통해 초기 로딩 시간을 줄이고 성능을 최적화할 수 있습니다. 전체 애플리케이션을 로드하는 대신 현재 사용자가 방문한 페이지에 필요한 코드만 로드하는 것을 의미하여 더 빠른 로딩을 가능하게 합니다.

- WebP 이미지는 일반적으로 JPEG 및 PNG보다 더 효율적인 압축을 제공하면서도 높은 품질을 유지합니다. 자주 사용되는 이미지를 효율적으로 압축하기 위해 webP로 변경하였습니다.
3. Profiler를 활용해서 불필요한 렌더링 최적화 / compressor.js로 이미지 압축
- 프로파일러를 사용하여 컴포넌트 렌더링 최적화를 진행했습니다. 특히 글쓰는 곳이나 그림을 그리는 부분은 마우스를 움직이거나 글자를 입력할 때 마다 헤더나 모든 레이아웃이 리렌더링이 되는 것을 알 수 있었습니다. 따라서 React.memo를 사용하여 컴포넌트를 캐싱했습니다.

- 마지막으로 compressor.js를 사용하여 0.02mB로 압축 후 서버에 보내니 하나의 이미지 로딩시간이 최대 50% (6ms->3ms)단축되었습니다.

🗣️ 유저 피드백 & 반영

페이지 이동 개선 [철민] - 문제 -
메인페이지와 커뮤니티 페이지에서 상세페이지로 이동이 되는데, 상세페이지에서 뒤로가기를 눌렀을 때 무조건 메인으로 가서 커뮤니티 페이지에서 이동한 사용자는
다시 커뮤니티 페이지를 클릭해야 하는 불편함이 발생했습니다.

- 해결 -
boolean 값을 가지는 전역 state를 만들어, 메인페이지에서 상세페이지로 이동되면 값을 false로 바꾸고 커뮤니티 페이지에서 이동되면 값을 true로 바꾸는 로직을 구성했습니다
상세페이지에서 만약 이 값이 true면 뒤로가기를 눌렀을 때 커뮤니티 페이지로 가게끔 하고, false면 메인페이지로 가게끔 로직을 재구성 했습니다.
input range 사용자 경험 개선 [지우] - 문제 -
input range를 통해 자신의 감정을 선택하고 다음 페이지로 이동했다가 다시 뒤로가기를 누르면 input range의 값이 초기화되어어서 사용자에게 불편감을 초래

- 해결 -
input range의 value값을 전역 상태 라이브러리인 recoil을 사용하여 상태를 저장하니 페이지 이동 후 다시 돌아와도 value값이 변하지 않았습니다.
카카오 공유하기 기능 추가 [덕용] - 피드백 -
"나의 감정 일기를 인스타나 카카오로 공유 할 수 있으면 좋을 것 같습니다"

- 해결 -




Kakao develop에서 공유하기를 위한 세팅을 해주었고 공식문서에 따라 공유하기 기능을 만들었다.


About

final-project-front

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • TypeScript 96.5%
  • JavaScript 1.9%
  • CSS 1.3%
  • HTML 0.3%