logo Image

Connection

FE: 2명, BE: 2명, Designer: 1명

( 2023.09 ~ 진행중 )

댄서와 수강생을 연결해주는 웹 플랫폼으로 유저의 요구를 이해하기 위해 설문조사 결과를 기반으로 기획됐습니다.

강사들이 자신의 클래스를 종합적으로 관리하고 효과적으로 홍보할 수 있도록 지원함으로써, 강사들의 업무 효율성을 높여줍니다.
수강생들은 다양한 클래스와 강사들 중에서 자신의 필요와 취향에 맞는 수업을 자유롭게 선택할 수 있는 기회를 얻게 됩니다.

현재 이 플랫폼은 실제 사업자 등록 절차 진행 중에 있습니다.

사용 기술

Next
(App Router) SEO 최적화를 위한 동적 메타 데이터 생성, 서버 사이드 렌더링을 포함한 서버 컴포넌트, 링크 사전 가져오기(Link prefetch), 이미지 최적화 (Image), 경로 처리(Route Handler), 경로 가로채기(Intercepting Routes), 병렬 경로 처리(Parallel Routes) 등의 고급 기능을 활용하기 위해 사용하였습니다.
TypeScript
컴파일 단계에서 타입 오류를 사전에 발견하여 프로젝트의 안정성을 높이기 위해 사용하였습니다.
Tailwind CSS
서버 컴포넌트에서 CSS를 사용하기 위해 zero-runtime 특성이 있는 Tailwind CSS를 선택했습니다.
Zustand
사용이 간단하고 가볍고 간결한 코드로 전역 상태 관리가 가능하여 선택하였습니다.
React Query
서버 상태 관리를 간소화하고 불필요한 네트워크 요청을 줄이며, 데이터 페칭, 캐싱, 동기화 및 서버 상태 업데이트를 편리하게 처리할 수 있도록 도입하였습니다.
React Table
성능 최적화와 유연한 커스터마이징이 가능해 데이터 테이블을 쉽게 구성하고 관리하기 위해 사용하였습니다.
React Hook Form
회원가입, 강사 등록, 클래스 작성 시 등 복잡한 폼 데이터의 효율적 관리와 유효성 검사를 위해 사용하였습니다.
Framer Motion
React 애니메이션 라이브러리로, 부드럽고 자연스러운 애니메이션을 구현하기 위해 사용하였습니다.
Naver Api
네이버 지도 및 네이버 소셜 로그인 기능을 위해 사용하였습니다.
Firebase
Service worker를 이용하여 웹 푸쉬 알림을 도입하기 위해 사용하였습니다.
PWA
프로그레시브 웹 애플리케이션을 구현하여 네이티브 앱과 유사한 사용자 경험을 제공하기 위해 사용하였습니다.
Socket.io
실시간 양방향 통신을 구현하여 실시간 채팅, 알림 등의 기능을 제공하기 위해 사용하였습니다.
Jest
단위 테스트와 통합 테스트를 효율적으로 작성하고 안정성을 높이기 위해 사용하였습니다.
Vercel
프론트엔드 애플리케이션을 배포하고, 자동화된 CI/CD 파이프라인을 구성하기 위해 사용하였습니다.
Docker
컨테이너 기술을 사용하여 애플리케이션의 환경 일관성을 유지하고, 배포와 스케일링을 용이하게 하기 위해 사용하였습니다.
GitHub Actions
자동화된 워크플로우를 설정하여 CI/CD 파이프라인을 구축하고, 코드 품질을 유지하기 위해 사용하였습니다.
Sentry
에러 모니터링과 로그 수집을 위해 도입하였습니다. 디스코드 웹훅과 연동하여 에러 상황을 모니터링하고 대응하였습니다.
StoryBook
컴포넌트의 문서화 및 디자이너와의 효율적인 협업을 위해 사용하였습니다.
Eslint
팀원과의 코드 품질과 일관성을 유지하기 위해 코드 린팅 도구로 사용하였습니다.

개발 과정

Storybook을 활용한 컴포넌트 문서화 및 UI 리뷰 간편화

개발과정 이미지
개발과정 이미지
개발과정 이미지
개발과정 이미지
개발과정 이미지
  • 디자이너와의 협업과 UI 리뷰를 간편화하기 위해 Storybook을 도입하고 공통 컴포넌트를 문서화하였습니다. 이를 통해 각 컴포넌트의 상태를 시각적으로 확인하고 테스트할 수 있게 되었으며, 디자이너와의 원활한 의사소통이 가능하게 되었습니다. 또한, 컴포넌트의 기능과 사용 방법에 대한 명확한 가이드라인을 제공함으로 팀원 모두가 일관된 이해를 바탕으로 보다 효율적으로 개발을 진행할 수 있게 되었습니다.
  • Storybook Link
  • 디자인 시스템 구축을 통해 디자인의 일관성이 높아지고 컴포넌트의 재사용성이 증가하였습니다.
  • 디자인 시스템 Figma Link

검색 필터 및 검색 페이지 구현

검색 필터 및 검색 페이지 구현-mobile-동작-gif
  • 강사, 클래스, 패스권을 검색할 수 있는 필터를 포함하여 구현된 페이지입니다. 이 필터에는 전체 검색 지역, 장르, 평점, 가격, 지정 날짜, 인원, 진행 방식, 요일, 시간대가 포함되어 있으며, 특정 키워드로도 검색이 가능합니다.
  • Search Params를 이용하여 사용자가 선택한 특정 필터와 검색어를 기록, 페이지를 벗어났다가 다시 돌아와도 이전의 선택사항을 유지하도록 구현하였습니다.
  • 서버 컴포넌트를 통해 초기 데이터를 받아와 TTV(Time To view)를 개선하였고, 검색된 아이템들의 검색 엔진 최적화(SEO)를 향상시킬 수 있는 장점을 제공하였습니다.

트러블 슈팅

이슈: 검색 아이템 페이지로 이동 후 기존 검색 페이지로 복원 시 무한 스크롤 아이템이 사라져 scrollRestoration이 되지 않는 문제

trouble video
troubleshooting video

Next.js는 기본적으로 뒤로 가기 및 앞으로 가기 탐색 시 스크롤 위치를 자동으로 복원합니다. 그러나 기존에는 클라이언트 컴포넌트 내의 상태(state)에만 아이템을 저장했기 때문에, 검색 아이템 페이지로 이동 후 다시 돌아왔을 때 아이템이 사라져 스크롤 복원이 되지 않는 문제가 발생했습니다. 이를 해결하기 위해 react-query의 useInfiniteQuery를 사용하여 무한 스크롤을 구현하고, 검색 아이템을 react-query에 캐싱 하여 기존 페이지로 복원 시 기존 아이템이 유지되도록 변경했습니다.

수강생 및 강사 리뷰 관리 페이지

수강생 및 강사 리뷰 관리 페이지-mobile-동작-gif
  • 강사일 때는 본인에게 작성된 전체 리뷰를 각종 필터를 이용해 편리하게 확인할 수 있습니다. 마찬가지로, 수강생 입장에서도 작성한 리뷰를 각 필터를 통해 손쉽게 확인할 수 있고, 참여한 클래스에 대한 리뷰를 작성할 수 있는 페이지입니다.

좋아요 및 차단 (강사 & 클래스)

좋아요 및 차단 (강사 & 클래스)-mobile-동작-gif
  • 강사 및 클래스의 좋아요 관련 기능 및 관심 강사 및 클래스 페이지

트러블 슈팅

이슈: 강사 & 클래스 상세 페이지 방문 후 preview에서 관심 표시 진행 시 표시가 되지 않는 이슈

trouble video
troubleshooting video

강사 & 클래스 상세 페이지 방문 후 preview에서 관심 표시를 하면 Next.js의 fetch cache 기능 때문에 좋아요 표시가 되지 않는 문제가 발생했습니다. 이를 위해 관심 표시한 강사와 클래스의 id를 react-query의 전역 상태로 관리하고, Like 컴포넌트가 이 전역 상태를 구독하여 동기화함으로써 해결했습니다.

수강생 관리 페이지

수강생 관리 페이지-mobile-동작-gif
  • 본인의 클래스를 수강 중인 회원을 관리할 수 있는 페이지입니다. 각종 필터를 사용하여 해당 회원의 정보를 확인할 수 있으며, 회원의 요청 사항, 회원에 대한 메모 작성, 전체 회원에게 알림 보내기 기능, 전체 회원 정보를 엑셀로 다운로드하는 기능을 제공합니다.
  • React Table 라이브러리를 활용하여 회원 정보 데이터 테이블을 쉽게 구성했으며, React Table의 엑셀 내보내기 기능을 사용했습니다.
  • 회원에 대한 메모를 작성할 때 서버 부담을 줄이기 위해 디바운스(debounce) 기능을 활용했습니다.

쿠폰 사용

쿠폰 사용-mobile-동작-gif
  • 유저가 쿠폰을 다운로드할 수 있는 기능과 다운로드한 쿠폰을 확인할 수 있는 관리 페이지, 그리고 쿠폰을 적용한 결제 기능을 구현했습니다.
  • 결제 페이지에 접근 시, 유저가 보유 중인 중복 쿠폰과 중복 불가능 쿠폰 목록을 확인하고 계산 로직을 이용하여 최대 할인을 제공하는 쿠폰들을 자동으로 적용하도록 구현했습니다.

쿠폰 관리

쿠폰 관리-mobile-동작-gif
  • 이 페이지에서는 강사가 자신의 클래스에 대한 쿠폰을 생성하고 수정할 수 있습니다.
  • react-hook-form을 사용하여 쿠폰의 폼 데이터를 효율적으로 관리하였습니다.
  • 클래스 등록 섹션에도 쿠폰 생성 기능이 포함되어 있어, 이를 공통 컴포넌트로 구현하였습니다.

패스권 구매 및 사용

패스권 구매 및 사용-mobile-동작-gif
  • 사용자가 패스권을 구매하고, 구매한 패스권의 현재 정보를 확인할 수 있는 관리 페이지를 구현했습니다. 또한, 구매한 패스권을 사용하여 클래스를 등록하는 기능도 구현하였습니다.
  • 결제를 간편하게 진행하기 위해 토스 페이먼츠를 도입했습니다.

패스권 관리

패스권 관리-mobile-동작-gif
  • 이 페이지는 강사가 패스권을 생성하고 수정할 수 있으며, 패스권 정보도 각 필터에 따라서 조회할 수 있는 관리 페이지입니다.

헤더 컴포넌트

트러블 슈팅

이슈: 헤더 컴포넌트의 프로필이 페이지 이동 및 새로고침 시 Hydration Delay 동안 비로그인 상태처럼 보이는 문제

trouble video
troubleshooting video

Zustand Store에 유저 정보를 저장하고, 해당 유저 정보를 프로필 컴포넌트가 구독하여 조건부 렌더링을 수행합니다. 이로 인해 pre-rendering 단계에서는 로그인 이전 상태로 보이게 되고, hydration이 완료된 후에야 Zustand 유저 Store가 업데이트되면서 Hydration Delay 동안 비로그인 상태처럼 보이게 됩니다.

이 문제를 해결하기 위해, 프로필 컴포넌트가 Zustand 상태에 의존하지 않도록 하고 서버 컴포넌트에서 유저 정보를 props로 전달받아 프로필 컴포넌트도 pre-rendering이 되도록 구현했습니다.

회원 정보 수정

회원 정보 수정-mobile-동작-gif
  • 이 페이지에서는 회원의 프로필, 닉네임, 이메일, 휴대폰 번호 정보를 수정할 수 있습니다.
  • 프로필 변경 시 어색하지 않고 자연스러운 변경 옵션이 나타나도록 하기 위해서 Framer Motion을 활용해 부드럽고 자연스러운 애니메이션을 구현했습니다.

지도 공통 컴포넌트 구현

지도 공통 컴포넌트 구현-mobile-동작-gif
  • 네이버 지도 API와 Vworld API를 활용해 지도 공통 컴포넌트를 구현하였습니다.
  • 특정 강의 장소가 있는 지도: Vworld API를 통해 특정 강의 장소의 좌표 값을 받아와 네이버 지도에 표시하였습니다. 또한, 네이버 지도 API가 출발지와 도착지 자동 입력 기능을 지원하지 않기 때문에, InfoWindow에서 출발지와 도착지를 선택하면 네이버 지도 검색창에 자동으로 입력되도록 네이버 지도 searchParams를 분석하여 구현하였습니다.
  • 협의되지 않은 강의 장소의 지도: Vworld API에서 시, 군, 구의 Polyline 값을 받아와 네이버 지도에 표시하도록 구현하였습니다. Next.js 라우터 핸들러를 사용하여 Polyline 데이터를 사전에 처리하고 클라이언트 측으로 전송하였습니다. 또한 장소 위치가 여러 곳으로 설정될 때 Promise.all을 사용하여 Polyline 데이터를 가져오는 요청 시간을 단축하였습니다.

네이버 소셜 로그인 및 회원가입 구현

네이버 소셜 로그인 및 회원가입 구현-mobile-동작-gif
  • 네이버의 oauth2.0 api를 활용하여 소셜 로그인 및 회원가입 기능을 구현하였습니다. 이를 통해 사용자가 기존의 소셜 계정을 활용하여 더욱 빠르고 편리하게 서비스를 이용할 수 있도록 하였습니다.

실시간 채팅

실시간 채팅-mobile-동작-gif
  • Socket.io를 이용하여 강사와 수강생 간의 실시간 채팅을 구현했습니다. 소켓 연결을 통해 상대방의 현재 활동 여부 및 최종 접속 시간을 파악하여 표시했습니다.
  • Socket.ioreact-query setQueryData를 이용해 채팅 전송 및 수신 시 채팅 카운트, 채팅 내용, 채팅방 목록 등을 동기화했습니다.
  • Framer Motion 라이브러리를 이용 채팅 모달의 크기 및 위치를 자유롭게 수정할 수 있도록 구현했습니다.

실시간 알림

실시간 알림-mobile-동작-gif
  • 알림 이벤트를 Socket.io를 이용하여 관리하였습니다. 강사는 수강생이 수업을 신청하거나 채팅을 보낼 때 알림을 받게 되며, 수강생은 강사의 전체 공지사항, 수업 신청 알림, 관심 있는 강사의 새로운 클래스, 쿠폰 및 패스권의 만료 기한 등 다양한 알림을 받을 수 있습니다.
  • Socket.io와 react-query의 setQueryData를 활용하여 알림을 동기화하였습니다.

클래스 등록 페이지

클래스 등록 페이지-mobile-동작-gif
  • 클래스 등록을 위한 인터페이스를 제공합니다. 이는 사진 업로드, 카테고리 설정, 상세 설명, 일정 및 공지사항 작성, 장소 지정, 가격 설정 등 다섯 가지 주요 섹션으로 구성되어 있습니다. 사용자가 각 섹션 간 이동할 때, 입력한 정보는 자동으로 임시 저장되어 데이터 손실을 방지합니다.
  • react-hook-form 라이브러리를 도입하여 각 섹션의 데이터 관리 효율성을 극대화하였습니다. 이를 통해 사용자 경험을 개선하고, 폼 관리의 정확성과 속도를 향상시켰습니다.

트러블 슈팅

이슈: 각 섹션의 파일 번들 크기가 과도하게 크다는 문제가 있었습니다.

Dynamic Import 기술을 적용하여 필요한 섹션만을 실시간으로 로드하도록 최적화하였습니다. 이 접근 방식은 전체 페이지의 로딩 시간을 단축시키고, 성능을 크게 향상시켰습니다. 사용자는 더 빠르고 원활한 인터페이스를 통해 클래스를 효율적으로 등록할 수 있게 되었습니다.

PWA 연결 및 설치 방법 페이지

PWA 연결 및 설치 방법 페이지-mobile-동작-gif
  • 원활한 사용자 경험을 제공하기 위해 PWA(Progressive Web App) 기술을 도입했습니다.
  • 사용자가 처음 접속할 때, display-mode: standalone을 통해 PWA의 설치 여부를 확인한 후, 설치되지 않았다면 PWA 설치를 유도하는 모달을 표시합니다. 만약 beforeinstallprompt 이벤트가 지원된다면, 앱 설치 버튼을 제공하고, 그렇지 않을 경우 상세한 설치 방법을 안내하는 페이지로 이동할 수 있는 버튼을 제공합니다. 이를 통해, 안드로이드와 iOS에서의 PWA 설치 방법을 사용자에게 명확히 안내하여 설치를 유도했습니다.

FCM 연결 및 웹 푸쉬 알림 구현

FCM 연결 및 웹 푸쉬 알림 구현-mobile-동작-gif
  • 웹 푸시 알림 기능을 구현하기 위해 FCM(Firebase Cloud Messaging)에 연결을 진행하였습니다.
  • Firebase의 Service Worker를 활용하여 웹 푸시 알림 기능을 구현하였습니다.

트러블 슈팅

이슈: Next.js의 public 폴더 내에서 환경 변수(env) 사용 시 'ReferenceError: process is not defined'라는 에러 발생

Next.js의 public 폴더 내에서 환경 변수를 사용할 때 발생하는 이슈로, Firebase의 Service Worker가 Next.js의 public 폴더 내에서 작동하기 때문에 발생하는 문제였습니다. 이 문제를 해결하기 위해 dotenv 패키지를 사용하여 빌드 시점에 process.env의 내용을 가져와 Firebase의 키값을 안전하게 숨길 수 있게 되었습니다. 이 방법을 통해 Firebase 키를 효율적으로 관리하며 보안성을 높일 수 있었습니다.

강사 등록 페이지

강사 등록 페이지-mobile-동작-gif
  • 이 페이지는 강사 등록을 위해 설계되었으며, 인증과 소개 두 가지 섹션으로 구분됩니다.
  • 인증 섹션은 추후 사업자 등록을 완료한 후 PASS 시스템과 연동될 예정입니다.
  • react-hook-form을 사용하여 각 섹션 데이터를 효율적으로 관리하였습니다.

강사 상세 페이지

강사 상세 페이지-mobile-동작-gif
  • 이 페이지는 강사에 대한 정보를 제공하며, 강사의 주된 수업 지역, 가르치는 장르, 소속 기관, 인스타그램 게시물, 자기소개, 경력, 현재 진행 중인 강의, 판매 중인 패스권, 그리고 수강 후기를 확인할 수 있습니다.
  • SEO 최적화를 위해 Next.js의 동적 메타 데이터 생성 기능(generateMetadata)을 활용하였습니다.

트러블 슈팅

이슈: Next.js에서 동적 메타 데이터 생성 시 중복 API 요청으로 인한 서버 리소스 낭비

Next.js에서 동적 메타 데이터를 생성하는 과정에서, generateMetadata 함수와 Page 컴포넌트가 각각 별도로 API 요청을 수행하게 되었습니다. 이로 인해 동일한 API 요청이 두 번 발생하여 서버 리소스의 낭비가 발생했습니다. 이러한 문제를 해결하기 위해 React Cache를 사용하여 fetch 요청의 결과를 캐시하고 동일한 데이터를 재사용하는 방법을 도입했습니다. 이 방법을 통해 단 한 번의 요청으로 데이터를 획득하고, 해당 데이터를 페이지와 generateMetadata 함수에 모두 전달할 수 있게 되었습니다.

강사 수정 페이지

강사 수정 페이지-mobile-동작-gif
  • 이 페이지는 강사가 본인의 소개와 정보를 수정할 수 있습니다.

캐러셀 공통 컴포넌트 구현

개발과정 이미지
개발과정 이미지
  • 커넥션 프로젝트에서 사용되는 다양한 캐러셀을 위한 공통 컴포넌트를 직접 개발하였습니다.
  • 이미지 캐러셀의 사용이 빈번하므로, 이미지 최적화를 위해 캐러셀 컴포넌트가 priority 값을 받아 해당 값에 맞는 이미지 요소만 우선 렌더링하고, 캐러셀이 활성화되면 나머지 이미지를 렌더링 하는 방식으로 구현했습니다. 이를 통해 초기 렌더링 속도를 향상시키고 리소스 사용을 감소시켰습니다.
  • JSDoc을 작성하고 스토리북을 구축하여 다른 개발자들이 쉽게 사용할 수 있도록 문서화를 진행하였습니다.

access Token 관리 및 refresh Token 재발급 로직 구현

개발과정 이미지
  • accessToken이 만료되거나 유효하지 않을 때, refresh Token을 사용하여 accessToken 재발급을 시도하는 로직을 구현했습니다. 재발급에 성공하면 실패한 API 요청을 다시 시도하고, 재발급에 실패하면 로그인 페이지로 이동하도록 처리했습니다.
  • React Query를 활용해 API 요청 상태를 관리하며, 401 오류 발생 시 자동으로 토큰 재발급을 시도하고 관련 에러를 처리하는 로직을 구현했습니다. 이를 통해 사용자 인증 실패 또는 세션 만료 시 자연스러운 사용자 경험을 제공했습니다.

미들웨어로 token 검증 및 protected route 구현

개발과정 이미지
  • Next.js의 미들웨어를 이용하여 accessToken을 검증하는 로직을 구현했으며, 검증에 실패할 경우 refreshToken을 이용하여 accessToken을 재발급하는 과정을 진행하였습니다. 또한, 토큰을 검증하여 각 경로(route)에 대한 접근 가능 여부를 판단하는 protected route도 구현하였습니다.

usePagiNation 공통 커스텀 훅 구현

개발과정 이미지
개발과정 이미지
개발과정 이미지
개발과정 이미지
개발과정 이미지
개발과정 이미지
개발과정 이미지
개발과정 이미지
개발과정 이미지
개발과정 이미지
  • 해당 프로젝트에서는 Pagination 기능을 자주 사용하고 있으며, 백엔드의 Pagination API는 커서 페이징 방식을 채택하고 있습니다. 특정 페이지(예: 4페이지)로 직접 이동할 경우, 이전 페이지의 첫 번째 항목 ID와 마지막 항목 ID가 필요합니다. 이 때문에 새로고침, 뒤로 가기, 앞으로 가기 등의 브라우저 이벤트를 통해 페이지네이션 기능이 있는 페이지로 돌아올 때, 4번째 페이지로 재진입하는 데 문제가 발생했습니다. 이 문제를 해결하기 위해 Pagination에 특화된 공통 커스텀 훅을 개발했습니다.
  • 해당 훅을 사용하면, 초기 데이터는 SSR로 처리되어, pre-rendering의 이점을 챙기고페이지네이션 옵션과 페이지 번호를 searchParams에 저장합니다. 이를 통해 새로고침, 뒤로 가기, 앞으로 가기 등의 브라우저 이벤트를 통해 페이지네이션 기능이 있는 페이지로 돌아올 때, 사용자가 설정한 옵션과 페이지 상태를 유지할 수 있도록 구현되었습니다.
  • JSDoc을 활용해 문서화를 진행함으로써 다른 개발자들이 이 훅을 보다 쉽게 사용할 수 있게 하였습니다.

디자인