[사이드 프로젝트] Next/TensorFlow/springBoot 감정 기반 추천 사이트 MoodSync 후기

2025. 7. 7. 17:22·Kh-academy

 

이번 포스팅에서는 제가 팀원으로 참여하여 개발한 '감정 기반 활동 추천 애플리케이션 MoodSync' 프로젝트의 개발 과정과 주요 기능, 그리고 기술적 도전 과제들을 회고해보려 합니다. 짧은 개발 기간 속에서도 팀원들과 함께 많은 것을 배우고 성장할 수 있었던 소중한 경험이었습니다.

 


1. 프로젝트 개요: MoodSync, 당신의 감정에 맞는 추천을!

MoodSync는 사용자의 현재 감정을 분석하여, 그 감정에 가장 적합한 음악, 활동, 도서, 그리고 YouTube 비디오를 추천해주는 서비스입니다. Next.js와 TypeScript를 기반으로 프론트엔드를 구축하고, Spring Boot 백엔드와 TensorFlow.js 기반의 머신러닝 서버를 연동하여 지능형 추천 시스템을 구현했습니다.

주요 기술 스택:

  • 프론트엔드: Next.js (App Router), TypeScript, React Hooks (useState, useEffect, useRef, useCallback), Zustand, Tailwind CSS, shadcn/ui, framer-motion, @hello-pangea/dnd, face-api.js
  • 백엔드: Spring Boot (Java), Node.js (TensorFlow.js 서버)
  • 데이터베이스: Oracle
  • 인증: JWT (JSON Web Token) 기반 인증

2. 핵심 기능 구현 상세

2-1. 메인 페이지: 직관적인 감정 선택 및 추천 요청

MoodSync의 메인 페이지는 사용자가 자신의 감정을 쉽게 입력하고 추천을 받을 수 있도록 설계되었습니다.

  • 감정 선택 기능: 다양한 감정 카드를 통해 사용자가 현재 기분을 선택할 수 있도록 구현했습니다. React의 useState를 활용하여 선택된 감정 상태를 관리하고, onClick 핸들러를 통해 부모 컴포넌트로 선택 이벤트를 전달하는 단방향 데이터 흐름을 적용했습니다. emotionValues prop을 통해 감정 인식 결과를 동적으로 표시하여 사용자에게 시각적 피드백을 제공합니다.
  • 스크롤 기반 플로팅 UI: useEffect와 IntersectionObserver API를 사용하여 메인 감정 선택 영역이 화면 밖으로 스크롤될 때, 우측에 작은 플로팅 감정 선택 UI가 나타나도록 구현했습니다. 이는 사용자가 페이지를 탐색하는 중에도 핵심 기능에 쉽게 접근할 수 있도록 UX를 개선한 부분입니다.
  • 감정 상태 동기화: 감정 카드 선택, 감정 슬라이더 조작, 그리고 얼굴 감정 인식 결과를 통해 입력된 감정 값들이 emotionValues 상태에 통합되어 관리됩니다. 특히, recommendationDirty 상태를 활용하여 감정 값이 변경되면 "감정 값이 변경되었습니다! 다시 감정 분석을 해주세요!"라는 경고 메시지를 표시하여, 사용자에게 최신 감정으로 추천을 받을 것을 유도했습니다.

2-2. 얼굴 감정 분석: AI/ML 기술 적용

  • face-api.js 및 TensorFlow.js 통합: face-api.js 라이브러리를 사용하여 얼굴 감지 및 7가지 기본 감정(neutral, happy, sad, angry 등)을 판별하는 모델을 웹 환경에 통합했습니다. 이 모델은 TensorFlow.js를 기반으로 하며, useEffect 훅을 통해 모델을 비동기적으로 로드하고, WebGL 백엔드를 우선 시도하여 GPU 가속을 활용하며, 실패 시 CPU 백엔드로 자동 전환되도록 폴백 로직을 구현하여 다양한 환경에서의 안정성을 확보했습니다.
  • 커스텀 감정 매핑 로직: face-api.js의 7가지 기본 감정을 '스트레스', '평온', '신남', '피곤함'과 같은 MoodSync의 6가지 사용자 정의 감정으로 변환하는 독자적인 매핑 로직을 설계했습니다. 각 감정은 여러 기본 감정의 조합과 가중치를 부여하여 추론되었으며, 0-100% 범위로 정규화하여 사용자가 직관적으로 이해할 수 있도록 했습니다.
  • 이미지 처리 및 시각화: 사용자가 업로드한 이미지에서 얼굴을 감지하고, HTML <canvas>를 사용하여 감지된 얼굴 주변에 사각형을 그려 시각적 피드백을 제공합니다. 분석된 감정 스코어는 부모 컴포넌트로 전달되어 메인 페이지의 감정 슬라이더에 반영됩니다.

2-3. 헤더 기능 및 전역 인증 상태 관리

  • Zustand를 활용한 전역 상태 관리: isLoggedIn, user, isAdmin, loading 등 인증 관련 상태를 Zustand 스토어를 통해 중앙에서 관리합니다. 이는 컴포넌트 간의 복잡한 prop drilling 없이 필요한 상태에 쉽게 접근하고 UI를 업데이트할 수 있게 합니다.
  • JWT 기반 인증: AppProviders 컴포넌트에서 useEffect를 통해 애플리케이션 초기 로딩 시 checkAuthStatus 함수를 호출하여 JWT(JSON Web Token) 기반의 인증 상태를 확인합니다. 백엔드(Spring Boot)에서 발급된 JWT는 HttpOnly 쿠키로 관리되어 XSS 공격으로부터 보안성을 강화합니다.
  • 로그인/로그아웃 기능: 헤더의 로그인/로그아웃 버튼은 isLoggedIn 상태에 따라 동적으로 변경되며, handleLogout 함수를 통해 Zustand 스토어의 로그아웃 액션을 호출하고 로그인 페이지로 리다이렉트됩니다.
  • 역할 기반 접근 제어: isAdmin() 함수를 통해 관리자 권한을 확인하고, 로그인된 관리자 사용자에게만 '관리자 대시보드' 링크를 노출하여 역할 기반 접근 제어를 구현했습니다.

2-4. 로그인 기능 구현

  • 폼 관리 및 유효성 검사: useState를 사용하여 아이디와 비밀번호 입력을 관리하고, 폼 제출 시 백엔드 API에 데이터를 전송하기 전 필수 입력값의 유무를 검증합니다.
  • 백엔드 API 연동: login API 함수를 통해 사용자 자격 증명을 백엔드로 전송하고, 성공 시 Zustand 스토어의 loginSuccess 액션을 호출하여 전역 인증 상태를 업데이트합니다.
  • 사용자 경험 최적화: 로그인 요청 중에는 로딩 스피너를 표시하고 버튼을 비활성화하여 중복 제출을 방지합니다. 로그인 실패 시에는 Alert 컴포넌트를 통해 구체적인 에러 메시지를 표시하며, 애니메이션 효과를 적용하여 사용자에게 즉각적인 피드백을 제공합니다. 이미 로그인된 사용자는 자동으로 메인 페이지로 리다이렉트됩니다.

2-5. API 통신 모듈 설계

  • Axios 인스턴스: axios.create()를 사용하여 api라는 Axios 인스턴스를 생성하고, baseURL 및 withCredentials: true (HttpOnly 쿠키 처리 필수)와 같은 공통 설정을 적용했습니다.
  • 요청/응답 인터셉터: 모든 HTTP 요청 및 응답을 가로채어 공통 로직을 적용합니다. 요청 인터셉터에서는 HttpOnly 쿠키가 자동으로 포함되므로 별도의 토큰 추가 로직이 필요 없으며, 응답 인터셉터에서는 401 (Unauthorized) 에러 발생 시 에러를 전파하여 각 컴포넌트에서 로그인 페이지로 리다이렉트하도록 처리합니다.
  • 타입스크립트 기반 API 함수: login, logout, getCurrentUser, register, sendVerificationEmail 등 각 API 엔드포인트에 대한 함수를 정의하고, UserDTO, LoginCredentials 등 요청/응답 데이터에 대한 TypeScript 인터페이스를 명확히 정의하여 코드의 안정성과 개발 생산성을 높였습니다.

2-6. TensorFlow.js 모델 학습 및 예측 백엔드

  • 모델 학습 (/train 엔드포인트):
    • 외부 API에서 학습 데이터를 가져와 tf.tensor로 변환하고, 훈련/검증 세트로 분할합니다.
    • tf.sequential()을 사용하여 신경망 모델을 정의하고, relu, softmax, dropout 계층을 추가하여 모델의 비선형성과 일반화 성능을 높입니다.
    • model.compile()로 옵티마이저(tf.train.adam), 손실 함수(categoricalCrossentropy), 평가 지표(accuracy)를 설정합니다.
    • model.fit()으로 모델을 훈련시키며, onEpochEnd 콜백과 조기 종료(Early Stopping) 로직을 통해 훈련 과정을 모니터링하고 과적합을 방지합니다.
    • 음악, 활동, 도서 모델을 Promise.all로 병렬 학습하고, 학습된 모델은 model.json 및 weights.bin 형태로 로컬 파일 시스템에 저장하여 재사용성을 확보합니다.
  • 모델 예측 (/predict 엔드포인트):
    • 클라이언트로부터 6가지 감정 값을 입력받아 tf.tensor로 변환합니다.
    • 미리 저장된 활동, 음악, 도서 모델을 로드하고, 각 모델에 대해 입력 텐서를 사용하여 예측을 병렬로 수행합니다.
    • 각 모델의 예측 결과(확률 분포 및 예측 클래스)를 추출하여 JSON 형태로 클라이언트에 반환합니다.
    •  

2-7. 컬렉션 관리 기능

사용자가 자신만의 추천 항목을 체계적으로 관리할 수 있도록 컬렉션 관리 기능을 구현했습니다.

  • 컬렉션 CRUD: 컬렉션의 생성, 조회, 수정, 삭제(CRUD) 기능을 제공합니다. CollectionFormModal을 통해 컬렉션의 이름, 설명, 공개 여부를 설정할 수 있습니다.
  • 드래그 앤 드롭(Drag & Drop) 순서 변경 및 이동: @hello-pangea/dnd 라이브러리를 활용하여 컬렉션 내부 아이템들의 순서를 직관적으로 드래그 앤 드롭으로 변경할 수 있도록 했습니다. 더 나아가, 아이템을 다른 컬렉션으로 드래그하여 이동시키는 기능까지 구현했습니다. 이 과정에서 onDragEnd 핸들러는 원본 컬렉션에서 아이템을 삭제하고 대상 컬렉션에 추가하는 두 단계의 백엔드 API 호출을 처리하며 데이터 일관성을 유지합니다.
  • 링크 공유 및 공개 컬렉션 조회: 공개 설정된 컬렉션은 고유 URL을 통해 다른 사용자와 공유할 수 있으며, 이 링크를 통해 다른 사용자의 공개 컬렉션을 조회하고 자신의 컬렉션으로 복사해 올 수 있습니다.
  • 사용자 피드백 및 애니메이션: framer-motion을 사용하여 컬렉션 카드 호버 효과, 성공 메시지 애니메이션, 그리고 컬렉션 아이템 뷰의 등장/삭제 애니메이션을 구현하여 시각적으로 매력적인 사용자 경험을 제공합니다.

 

기능 요약: 

◐ 헤더

- 로그인, 로그아웃 & 회원가입 (◐)

- 로그인에서 보낸 요청을 백앤드에서 jwt 이용한 인증  (◐)

- zustand 사용해 토큰 점검하는 컴포넌트 헤더 제작

 

● 메인 페이지

- 감정 슬라이더 정보 전송

- face-api의 정보와 감정 슬라이더값 매핑

- 감정 정보를 기반으로 활동 추천 (◐)

- 추천받은 활동 컬렉션에 저장 

- 화면 우측 플로팅 버튼

 

● 컬렉션 페이지

- 컬렉션 관리 (CRUD)

- 드래그를 이용한 컬렉션 순서 배치 후 자동저장

- 링크공유/ 공개 컬렉션 조회

 

○ 마이 페이지

- 감정상태 통계 차트 (일별/주간)

- 주간 추천 기록 

 

○ 관리자 페이지

- 도움말/문의하기/피드백 관리

- 모델 학습 

 

○ 감정기반추천

- 6가지 감정값을 JSON형태로 전달받아 모델 로드

- 다양한 종류의 콘텐츠를 추천


저는 이 프로젝트에서 주로 세가지 역할을 맡아 구현하였습니다!

  1. 헤더 전역변수를 사용한 로그인/로그아웃 상태관리
  2. 메인페이지 감정슬라이더 정보 전송 및 UI/UX
  3. 컬렉션페이지 CRUD 및 DND 기능

 

3. 구현 기능 상세

 

1. 구현기능설명 - 헤더

  • Zustand를 활용한 전역 인증 상태 관리
  1. Zustand 스토어로 애플리케이션 전반에 걸쳐 필요한 인증 관련 상태를 중앙에서 관리했습니다.
  2. 필요한 컴포넌트에만 상태를 전달하여 prop drilling 문제를 해결했습니다.
  3. Layout.tsx를 감싼 AppProviders.tsx 컴포넌트의 useEffect 훅을 사용하여 인증을 초기화했습니다.
  4. checkAuthStatus함수로 JWT를 확인하고, 백엔드API에 유효성 검사를 요청했습니다.
  • UI/UX 고려 
  1. App Router 방식에 맞춰 next/link를 사용하여 페이지 간의 클라이언트 사이드 내비게이션을 최적화했습니다.
  2. Tailwind CSS를 활용하여 반응형 헤더를 구축했습니다.
  3. 로딩 상태 시 스피너를 보여주고, 로그인 상태에 따라 다른 메뉴를 보여주는 등 명확한 시각적 피드백을 제공하여 사용자 경험을 개선했습니다.
  • 로그인/ 로그아웃 / 회원가입 
  1. 로그아웃 버튼 클릭 시 사용자 세션을 종료하고 router.push를 통해 로그인 페이지로 리다이렉트되도록 구현했습니다.
  2. 로컬스토리지에서 토큰을 제거하고 백엔드에 로그아웃 요청을 보냈습니다.
  3. 전역변수 상태를 받아 로그인/로그아웃 버튼을 조건부로 렌더링합니다.
  4. 로그인 / 회원가입 시 onSubmit이벤트로 SPA 특성을 유지하며 폼 제출을 처리했습니다.
  5. 백엔드 로그인 API에 userId, userPw를 전송했습니다.
  6. 로그인 성공 시 JWT포함한 사용자 정보를 httpOnly 쿠키와 함께 받습니다.
  7. useAuthStore에서 액션을 호출해 사용자 정보를 전역상태에 저장합니다.

 

▶ 코드 설명 : API 통신 모듈 설계 및 구현

  1. axios.create()를 사용하여 api라는 Axios 인스턴스를 생성했습니다. 
  2. 애플리케이션 내 모든 API 요청에 재사용해 일관된 설정을 적용하고 코드 중복을 방지했습니다.
  3. 요청 및 응답 인터셉터를 설정했습니다.
  4. 응답 인터셉터에서 401 상태 코드를 받은 경우 각 컴포넌트에서 상황을 처리할 수 있도록 제작했습니다.

 

2. 구현기능설명 - 메인페이지

  • 감정 선택/슬라이더 기능
  1. 메인페이지에서 6개의 감정값을 설정할 수 있게 화면에 출력하였습니다.
  2. 기본 클릭시 50%로 자동설정되고 슬라이더를 조절해 강도를 설정하게끔 했습니다.
  3. useState를 사용하여 UI와 데이터의 상태를 일관성 있게 유지 및 즉각적으로 반영되게 했습니다.
  4. 선택된 감정 상태와 감정 슬라이더 상태를 업데이트해 컴포넌트들이 동기화되도록 했습니다.
  5. face-api.js에서 감지된 감정 점수를 받아 업데이트하여 감정 인식 결과를 자연스럽게 UI에 통합했습니다.
  • face-api.js 값과 슬라이더값 매핑
  1. face-api.js가 제공하는 7가지 기본 감정 표현을 변환하는 매핑 로직을 설계했습니다.
  2. '스트레스', '평온', '신남', '피곤함'과 같은 복합적인 감정은 여러 기본 감정의 조합과 가중치를 부여하여 추론했습니다.
  3. 각 감정 스코어가 0-100% 범위 내에 있도록 Math.min(100, Math.max(0, value))를 사용하여 보정하였습니다.
  4. 사용자가 업로드한 이미지를 읽어와 <img> 태그에 표시하고, onLoad 이벤트를 활용하여 이미지가 로드된 직후 얼굴 감지 및 분석을 자동으로 실행하도록 구현했습니다.
  5. HTML <canvas> 요소를 사용하여 감지된 얼굴 주변에 사각형을 그리고, 감정 분석 결과를 시각적으로 오버레이하여 사용자에게 피드백을 제공했습니다. 
  6. 모델 로딩 실패, 얼굴 미감지, 분석 중 오류 등 다양한 예외 상황에 대한 try-catch 블록을 구현하여 애플리케이션의 안정성을 높였습니다.
  • 감정 데이터 서버 전송 및 추천 받기
  1. 감정 기반 추천 요청하기 클릭 시 백엔드 API로 슬라이더값을 전송하는 기능을 fetct API로 구현했습니다.
  2. 응답으로 받은 음악, 활동, 도서, YouTube 비디오 추천 데이터를 컴포넌트에 전달하도록 했습니다.
  3. 감정값이 수정되었을때 정보 갱신이 필요하다는 경고 메시지로 UX를 개선했습니다.
  4. 모달에서 API를 호출하여 추천 항목을 데이터베이스에 저장되도록 했습니다.
  • 스크롤에 따른 플로팅 UI 
  1. 감정 선택 영역이 뷰포트 밖으로 스크롤될시 플로팅 UI가 나타나도록 했습니다.
  2. useRef로 특정 DOM 요소를 참조하고 가시성 변화를 감지했습니다.

 

 코드 설명 : useEffect 스크롤 기반 UI 제어

  1. useRef 훅을 사용하여 메인 감정 선택 영역을 감싸는 div요소에 직접적인 참조를 생성했습니다.
  2. useEffect 내부에서 DOM요소의 현재 위치와 가시성 파악을 위해 사용했습니다.
  3. 컴포넌트 마운트 시 useEffect가 실행됩니다.
  4. IntersectionObserver 웹 API 를 사용하여, div요소를 비동기적으로 관찰합니다.
  5. observer 인스턴스가emotionSelectionRef.current가 가르키는 div 가시성 변화를 관찰하고, 콜백함수를 호출합니다.
  1. [entry]를 인자로 받아 관찰 대상 요소의 현재 상태에 대한 정보를 받습니다.
  2. 요소가 위로 스크롤되어 화면 밖으로 완전히 벗어났을 때를 판단합니다.
  3. 참인 경우 플로팅 UI를 보이도록 상태를 변경합니다.
  4. 상태가 변경되면, JSX 조건부 렌더링에 따라 화면 우측에 플로팅 UI를 표기합니다.

컬렉션

 

3.구현기능설명 - 컬렉션

  • 컬렉션 관리 기능
  1. 컬렉션의 이름, 설명, 공개여부, 아이템 유형을 카드 형태로 표시했습니다.
  2. 콜백 함수를 통해 상세 정보를 모달로 조회하도록 연결했습니다.
  3. 콜백 함수를 통해 이름,설명,공개여부 수정모달로 연결했습니다.
  4. 콜백 함수를 통해 컬렉션을 삭제 가능하도록 했습니다.
  5. 공개 설정된 컬렉션은 고유URL을 클립보드에 복사 가능하도록 구현했습니다. 
  6. AnimatePresence를 활용한 애니메이션 메시지로 UX를 향상시켰습니다.
  7. 새 컬렉션 만들기를 클릭 시 모달을 열어 사용자가 새로운 컬렉션을 생성할 수 있도록 합니다.
  • 공개 컬렉션 
  1. 공개 설정된 컬렉션들을 출력해 조회할 수 있도록 했습니다.
  2. 컬렉션 내용과 유저의 이름을 함께 출력합니다.
  3. 공개 컬렉션으로 들어간 경우 수정할 수 없도록 접근방식을 설정했습니다.
  4. 다른 사용자의 공개 컬렉션을 자신의 컬렉션으로 복사해 올 수 있도록 구현했습니다.

  • 컬렉션 관리 페이지 개발
  1. 컬렉션 목록 관리 및 사이드바 통합
  2. 사이드바 컴포넌트를 활용하여 사용자 컬렉션 목록을 사이드바에 표시했습니다.
  3. 각 컬렉션 버튼 클릭 시 상태를 업데이트하여 해당 컬렉션의 상세 아이템 뷰를 메인 영역에 동적으로 열고 닫을 수 있도록 구현했습니다.
  4. 새 컬렉션 생성을 클릭하여 모달로 새로운 컬렉션을 생성할 수 있도록 했습니다.
  •  드래그 앤 드롭을 통한 아이템 순서 변경 및 컬렉션 간 이동 기능
    1. react-dnd 라이브러리의 DragDropContext를 메인 콘텐츠 영역을 감싸도록 배치하여, 모든 컬렉션 아이템에 대한 드래그 앤 드롭 기능을 활성화했습니다.
    2. 원본 위치와 최종 목적지를 판별하여 같은 컬렉션 내 순서 변경인지 다른 컬렉션으로 이동인지 분석합니다.
    3. 컬렉션 내 순서 변경인 경우 해당 컬렉션 내부에서 아이템의 순서를 변경하고 API를 호출하여 변경된 itemOrder를 백엔드에 반영합니다.
    4. 다른 컬렉션으로 아이템 이동인 경우 원본 컬렉션의 아이템을 삭제하고, 대상 컬렉션에 아이템을 추가하고 순서를 재정렬, API를 호출해 데이터 일관성을 유지합니다.
    5. DND 작업이 성공적으로 완료되면 UI를 백엔드 데이터와 완벽하게 동기화합니다.

 

 

▶ 코드 설명 : onDragEnd 핸들러 동작 

  1. result 객체에서 source(시작위치), destination(최종위치), draggableId(아이템 id)를 추출하고, 유효성을 검사합니다.
  2. source.droppableId === destination.droppableId인 경우 같은 컬렉션 내 순서 변경 로직을 실행합니다.
  3. 원본 컬렉션의 아이템 배열을 복사하고 splice()로 원본 위치 아이템을 제거하고 새 위치에 삽입해 UI를 먼저 업데이트했습니다.
  4. 변경된 순서에 따라 itemOrder를 부여한 페이로드를 생성합니다.
  5. API를 호출하여 변경된 순서를 백엔드에 전송하고 DB에 업데이트합니다.
  6. source.droppableId === destination.droppableId에서 else인 경우 다른 컬렉션으로 아이템 이동 로직을 실행합니다.
  7. 드래그된 아이템을 원본컬렉션에서 삭제합니다.
  8. 드래그된 아이템의 DTO를 구성하고, API를 호출해 대상 컬렉션에 추가합니다.
  9. 원본 컬렉션과 대상 컬렉션의 itemOrder를 재정렬합니다.
  10. 전체 컬렉션 데이터를 새로고침해 최신 상태를 반영하도록 동기화합니다.
  11. try-catch로 DND작업 중 생기는 오류를 처리했습니다.

4. 프로젝트 트러블슈팅 경험 및 해결 과정

프로젝트를 진행하며 마주했던 기술적 난관들을 효과적으로 해결하며 문제 해결 능력과 시스템에 대한 이해를 심화시킬 수 있었습니다.

4-1. 드래그 앤 드롭(DND) 아이템 순서 변경 시 백엔드 페이로드 불일치 문제

  • 문제: 컬렉션 아이템의 드래그 앤 드롭 순서 변경 기능을 구현하는 과정에서, 프론트엔드에서 구성하여 백엔드로 전송하는 페이로드(DTO)의 필드 이름이 백엔드(Spring Boot)에서 기대하는 이름과 일치하지 않아 API 호출이 실패했습니다. 예를 들어, 프론트엔드에서는 collectionItemId로 데이터를 구성했지만, 백엔드에서는 id라는 필드명을 기대하고 있었습니다.
  • 해결: 백엔드 API 문서와 실제 서버 로그를 면밀히 분석하여, 백엔드에서 예상하는 정확한 DTO 구조와 필드명을 파악했습니다. 이후 onDragEnd 핸들러 내부에서 updateCollectionItemsFull API를 호출할 때, item.collectionItemId 대신 id: item.collectionItemId와 같이 백엔드에서 요구하는 필드명으로 페이로드를 재구성하여 전송하도록 코드를 수정했습니다.
  • 결과: 프론트엔드와 백엔드 간의 데이터 계약(Contract)이 일치하게 되어, 드래그 앤 드롭을 통한 컬렉션 아이템의 순서 변경 및 컬렉션 간 이동 기능이 성공적으로 작동하고 데이터가 백엔드에 정확히 반영되었습니다. 이 경험을 통해 프론트엔드-백엔드 연동 시 데이터 구조의 중요성과 디버깅 능력을 향상시켰습니다.

4-2. 페이지 첫 로딩 시 인증 상태 비정상 작동 문제

  • 문제: 애플리케이션 첫 로딩 시 사용자 토큰 유무를 확인하고 로그인 상태를 체크하여 헤더 UI에 반영하는 과정에서 비정상적인 동작이 발생했습니다. 초기 인증 상태 확인 로직이 중복 호출되거나, 헤더 컴포넌트가 인증 상태가 확정되기 전에 렌더링되어 잘못된 로그인 상태를 표시하는 문제가 있었습니다.
  • 해결: AppProviders.tsx에서 인증 상태를 확인하는 checkAuthStatus 함수가 중복 호출되던 문제를 수정하고, Zustand를 활용한 authStore.ts를 전역 상태 관리 스토어로 제작했습니다. AppProviders에서 useEffect를 사용하여 애플리케이션 시작 시 checkAuthStatus를 단 한 번만 호출하도록 보장하고, Header 컴포넌트가 authStore의 loading 상태를 구독하여 인증 상태 확인이 완료될 때까지 로딩 스피너를 표시하도록 컴포넌트 렌더링 순서를 조정했습니다.
  • 결과: 애플리케이션 첫 로딩 시 인증 상태가 정확하고 빠르게 확인되며, 로그인 상태에 따라 헤더의 로그인/로그아웃 버튼 및 사용자 정보가 정상적으로 표시되어 사용자 경험이 크게 향상되었습니다.

4-3. Axios 응답 인터셉터의 직접적인 리다이렉션 문제

  • 문제: 초기에는 lib/api/base.ts 파일의 Axios 응답 인터셉터에서 401 (Unauthorized) 에러 발생 시 window.location.href = '/user/login'을 사용하여 사용자를 로그인 페이지로 직접 리다이렉트하도록 구현했습니다. 하지만 Next.js의 App Router 환경에서는 이러한 브라우저 API를 통한 직접 리다이렉션이 클라이언트 사이드 내비게이션 흐름을 방해하거나, 서버 컴포넌트 컨텍스트에서 예상치 못한 부작용을 일으킬 수 있었습니다.
  • 해결: 응답 인터셉터에서 직접적인 리다이렉션 로직을 제거했습니다. 대신, 401 에러가 발생하면 단순히 Promise.reject(new Error('Unauthorized'))를 반환하여 에러를 호출자(calling component)에게 전파하도록 변경했습니다. 로그인 페이지로의 리다이렉션 책임은 이제 HomePage나 CollectionList와 같이 useRouter 훅을 사용할 수 있는 클라이언트 컴포넌트에서 try-catch 블록을 통해 에러를 감지하고 명시적으로 처리하도록 위임했습니다.
  • 결과: API 통신 모듈은 순수하게 데이터 요청/응답 처리와 공통 에러 전파에 집중하게 되어 관심사 분리(Separation of Concerns) 원칙을 지켰습니다. 이는 Next.js 환경에서 더욱 안정적이고 예측 가능한 라우팅 및 에러 처리 흐름을 구축하는 데 기여했습니다.

 

프로젝트 후기

 

 저는 이번 프로젝트에서 4명의 팀원과 함께 작업했습니다. 체계적인 일정 계획과 효율적인 업무 분배를 통해 프로젝트를 성공적으로 완수했습니다. 가장 힘들었던 작업은 face-api.js를 활용한 얼굴 감정 인식 및 커스텀 감정 매핑 로직을 구현했던 작업입니다. 사용자에게서 사진을 업로드받아 감정값을 출력하고, 프로젝트에서 사용하는 값으로 변환시키는 과정에 고민이 많이 되었습니다.

 또한 컬렉션 페이지에서 react-draggable 라이브러리를 사용하였는데, 요소를 드래그할 때마다 React의 상태를 관리하며 컬렉션 수정을 구현한 점이 매우 보람 있었습니다.

 프로젝트 도중 팀원이 프로젝트를 이탈하는 어려움이 있었지만, 팀원들과의 꾸준한 소통과 협력을 통해 부족한 부분을 보완하며 기한 내에 성공적으로 마무리할 수 있었고, 이 경험을 통해 협업 능력과 문제 해결 능력을 크게 향상시키며 개발자로서의 큰 뿌듯함과 성취감을 느낄 수 있었습니다!

 

이번 MoodSync 프로젝트를 통해 저는 프론트엔드 개발의 전반적인 과정은 물론, 백엔드 연동, 머신러닝 모델 활용, 그리고 팀 리더십까지 다양한 경험을 쌓을 수 있었습니다. 이 경험들을 바탕으로 앞으로도 끊임없이 학습하고 성장하며, 실무에서도 기여할 수 있는 개발자가 되겠습니다.

 

긴 글 읽어주셔서 감사합니다!

 

무드싱크 깃허브로 이동: 

https://github.com/Wjyuy/MoodSync

 

GitHub - Wjyuy/MoodSync

Contribute to Wjyuy/MoodSync development by creating an account on GitHub.

github.com

 

무드싱크 데모 페이지로 이동: 

https://mood-sync-41q7vg7mr-wjyuys-projects.vercel.app/

'Kh-academy' 카테고리의 다른 글

KH 정보교육원 빅데이터 기반(파이썬•자바•웹) 엘라스틱 검색엔진 개발자 과정 기록(2025.01.15 - 2025.07.14)  (0) 2025.02.18
'Kh-academy' 카테고리의 다른 글
  • KH 정보교육원 빅데이터 기반(파이썬•자바•웹) 엘라스틱 검색엔진 개발자 과정 기록(2025.01.15 - 2025.07.14)
우주녕
우주녕
개발 뉴비
  • 우주녕
    주연이의 일기장
    우주녕
  • 전체
    오늘
    어제
    • 분류 전체보기 (10)
      • Daily (0)
      • Cording (7)
      • Kh-academy (2)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    This
    자바
    생성자
    java
    레퍼런스변수
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
우주녕
[사이드 프로젝트] Next/TensorFlow/springBoot 감정 기반 추천 사이트 MoodSync 후기
상단으로

티스토리툴바