DYO 공부하는 블로그
[Re:Life] 프로젝트 회고 본문
1. 프로젝트 개요
🌌 Re:Life
"만약 그때 다른 선택을 했다면?"
AI가 시뮬레이션하는 평행우주적 인생 시나리오 서비스
Re:Life는 사용자의 과거 인생 선택을 기반으로, AI가 "만약 그때 다른 선택을 했다면?"이라는 평행우주적 인생 시나리오를 시뮬레이션하여 제공하는 웹 서비스입니다.
단순한 재미를 넘어, 실제 사회 통계와 개인 성향 데이터를 반영하여 현실적이고 구체적인 대안적 삶을 탐색할 수 있도록 설계되었습니다.
깃허브 레포지토리
GitHub - prgrms-web-devcourse-final-project/WEB5_6_OneTop_FE: 프로그래머스 웹개발 프론트엔드 6기 8회차 1팀 최
프로그래머스 웹개발 프론트엔드 6기 8회차 1팀 최종 프로젝트 프론트엔드 레포지토리입니다. Contribute to prgrms-web-devcourse-final-project/WEB5_6_OneTop_FE development by creating an account on GitHub.
github.com
배포 주소 ( 백엔드 서버 배포 중단 )
Re:Life
Re:Life는 당신의 인생 선택을 기록하고, 다른 선택을 했다면 어떤 평행우주가 펼쳐질지 보여줍니다. 지금 바로 시작해보세요! 시작하기
www.relife.kr
시연 영상
2. 프로젝트 시작
- 팀 선정
팀 선정부터 쉽지 않은 과정이긴 했다. 익숙한 프론트엔드 팀원들끼리의 프로젝트가 아니라 처음 보는 백엔드 팀원들을 선정하고 그 사람들과 컨텍해 팀을 이루어야 했다. 때문에 팀을 프론트팀끼리 구성하고, 팀 PR을 구성해야 했다. 매우 감사하게도, 프론트 팀에서는 원하는 팀원들에게 요청을 보냈을 때 바로 구성할 수 있었다. 팀 PR 문서는 다른 프론트팀이 너무 재미있게 짜서 그렇게 짜야 하나 싶었지만 우리 팀 스타일이 아닌 것 같아 최대한 해왔던 것을 잘 보여줄 수 있는 형태로 구성했다.

- 아이디어 선정
프로젝트 자체는 내 아이디어는 아니었고, 팀원의 아이디어로 결정된 아이디어였다. 사실 나는 처음엔 반대 의견이 더 컸었다. 프로젝트 기획 자체는 신선하고 재미있어 보였다. 그런데 문제라고 생각했던 점은
- 프로젝트 확장이 어려워 보임
- 기획은 신선하고 재미있지만 '유저 입력 선택지' 자체가 가진 유저 입력복잡도 증가 (어떻게 유저에게 UX로 납득되게 보여줄 것인지)
- AI 사용이 고정되지 않음으로서 결과값 불안정 위험성
위와 같은 이유로 프로젝트 아이디어 선정에서 반대 의견을 이야기했었으나, 백엔드 팀원들과 논의 과정에서 최종 결정된 아이디어가 되었다.
항상 내가 옳을 수는 없다. 백엔드 팀에서도 복잡도 제어에 자신감을 내비쳤었고, 이미 선택된 기획에서는 내가 납득해야 '원 팀'이 될 수 있다고 생각했었다. 내가 납득할 수 있었던 이유는 두 가지이다.
- 추후에 토큰과 같은 비용을 이용해 비즈니스 모델을 만들 수 있다.
- 무한히 펼쳐지는 유저 선택지에서 파생되는 다양한 기능들
이런 아이디어 선정 과정을 거치고 프로젝트를 시작하게 되었다.
- 기술 선정
TypeScript - 코드 작성 단계에서 타입을 걸러주는 강력함은 말할 것 없고, 사용하는 라이브러리들도 모두 Type에 강점이 있는 라이브러리였기 때문에 연계성이 좋다고 판단했다.
React - 팀원 모두 팀 활동을 거치면서 컴포넌트 기반의 작업의 재사용성과 작업 분리에 대해서 잘 이해하고 있었기 때문에 좋은 선택지라고 생각했다.
Next.js - 백엔드에서 많은 데이터를 받아오고 많은 페이지를 상정했기 때문에 서버 컴포넌트, 최적화 기능, 라우팅 구조, SEO에서 큰 장점이 있다고 생각했다. 때문에 Next를 사용하기로 결정하게 되었다.
Tanstack Query - 클라이언트에서의 캐시 관리에 큰 장점이 있는 데 더해 RSC에서도 dehydrate 패턴으로 데이터를 받아오는 데 큰 장점이 있다고 느껴
Axios - 간단하게 사용할 수 있는 fetch, 공용 인스턴스 구현 기능이 백엔드 협업에서 헤더 설정과 인터셉터를 이용한 전처리에 큰 장점이 있다고 느껴 선택했다.
Zod - 백엔드 협업에서 데이터 구조는 빈번하게 있을 수 있다고 판단해 런타임 타입 검증이 필요하다고 생각해 Zod를 이용하려고 했다.
react-hook-form - 유저 입력이 많은 서비스였기 때문에 유효성 검증에 react-hook-form을 이용하면 유저 입력 검증을쉽게 구현할 수 있다고 생각해 선택했다.
GSAP - 이미 GSAP 애니메이션에 익숙한 팀원들이 많기도 했고(나 포함), 라이브러리의 다양한 기능이 무료로 풀렸기 때문에 강력한 기능들을 사용해보고 싶은 욕심이 있었다. 스크롤 제어에서의 강력함은 말 할 것도 없고.
- 컨셉 선정 및 디자인
'평행우주'라는 컨셉에 맞게 'multiverse'하면 우주의 이미지가 떠오르듯이, 우주의 모습을 모티브로 작성했다. 먼저 레퍼런스를 가져와 초기 컨셉을 정하고, 와이어프레임 구성 후에 디테일 작업을 했다.


3. 아키텍쳐
- 프로젝트 아키텍쳐

전체 아키텍쳐는 다음과 같다. 위 아키텍쳐는 조금 백엔드 중심으로 작성되어 있으니, 프론트 중심이면서 단순화되어 있는 아키텍쳐는 아래와 같다.

Next를 사용하므로, 배포 서버에서 RSC 렌더링과 server action의 책임을 가지고 있고, 비즈니스 로직들은 Nginx 프록시를 거쳐 메인 서버에서 처리한다. OAuth는 Github와 Google을 이용했다. 클라이언트에서 url을 연결해주고, 로그인 성공 시 OAuth 서버는 리다이렉트를 거쳐 메인 서버에서 클라이언트로 cookie를 내려준다.
4. 내가 구현한 파트, 트러블슈팅
- 로그인, 회원가입
⭐ 구현 내용

로그인 / 회원가입의 form은 react-hook-form과 zod를 이용해 유효성을 관리해 유저에게 즉시 피드백을 줄 수 있도록 했다.
로그인 / 회원가입을 구현하면서 신경썼던 점은 로직을 next의 server action으로 분리하는 것이었다. server action을 이용하면 서버 레벨에서 요청하게 된다. 그 때문에 로그인 / 회원가입 로직을 노출하지 않고 숨길 수 있다.
백엔드에서 JWT를 사용한 인증을 사용할 것이라고 예상하고 프로젝트 시작 전에 미리 준비해뒀었는데, Session + CSRF 토큰 기반으로 관리하기로 결정돼 Session ID를 이용해 구현했다.
🥲 구현에서의 어려움
- next를 학습하기는 했지만 next에 익숙하지 않았기 때문에 서버 레벨의 요청, cookie를 cookieStore로 수동 관리하는 것에 어려움이 있었다.
- 또한 모든 DB 요청이 인증 권한이 없으면 요청이 불가능했고, 백엔드에서 CSRF 구현 관련으로 이슈가 많아 로그인 구현이 지연되었기 때문에 최대한 빠르게 작업을 마무리 해 줄 필요가 있었다.
익숙하지 않은 개념(next server action 요청, CSRF 토큰)에 시간적 압박까지 있어 구현하면서 정말 스트레스를 많이 받았었다.

✅ 아쉬운 점
로그인 구현 전에는 요청 권한을 임시적으로 모두 낮춰주는 것을 백엔드에 요청했어야 했는데, 그 부분이 아쉽다. 그렇게 했다면 처음 구현에서 훨씬 완성도 있는 코드를 만들 수 있었을 것 같고, mock 데이터 관리 소요도 훨씬 줄었을 것 같다.
- 공용 fetch 인스턴스 유틸
⭐ 구현 내용

CSRF 토큰 인증을 요구했기 때문에 서버에서의 요청(Next의 fetch)과 클라이언트 요청(Axios) 모두 전처리가 필요할 것 같았다. 로그인을 구현하기에 앞서 먼저 구현해야 할 필요성을 느끼고 구현했다.
- 구현의 핵심 가치
1. 두 개의 인스턴스는 이름만 다를 뿐 같은 파라미터 구조와 요청 방식을 가져야 한다. (단순화)
2. CSRF 인증 실패 시(403 에러)에 CSRF 토큰을 재수령하고 재요청하도록 설정한다. (안정성)
3. 에러에 대해서는 중계만 하고, 에러 처리 핵심 로직은 사용되는 위치에서 구현한다 (다양성)
4. 모든 요청에 대해 이 인스턴스를 사용하도록 한다 (일관성)
위 가치들을 기본으로 두고, 공용으로 사용할 nextFetcher.ts와 api.ts를 구현했다.
🥲 구현에서의 어려움
CSRF 토큰을 이용하는 건 크게 어렵지 않다고 생각했었다. interceptor를 붙여 헤더에 토큰을 삽입하고, 403에러가 뜰 경우 retry하는 로직을 짜면 쉽게 구현할 수 있을 것이라고 예상했는데, 늘 코드 짜는 게 그렇듯 계획대로 되지는 않는다.
정상적으로 구현했다고 생각했는데, 지속적으로 403에러가 발생했다. 원인은 나는 백엔드에서 내려주는 토큰 이름이 'CSRF-TOKEN'이라고 인식하고 있었다. 그런데 실제로 내려오는 토큰 이름은 'XSRF-TOKEN'이었다. 백엔드에서는 쿠키 이름을 직접 지정해 관리하는 cookieStore에 대한 인식이 없어 자동지정 명령으로 저장될 줄 알아서 공유하지 않았다고 하고, 나는 너무 개인적으로 쿠키 이름을 생각했던 것 같다.
역시 개발은 소통이라는 생각이 다시 들었다.
✅ 아쉬운 점
소통을 좀 더 빠르게 했다면 어땠을까 하는 아쉬움이 든다. 아예 처음부터 문제해결을 같이 접근했더라면 하는 생각. 혼자 해결했던 과정은 다음과 같다.
1. 정상적으로 구현 된 것 같은데 안 되네?
2. 한참 이리저리 바꿔보기
3. 혹시 모르니 Axios 요청으로 요청해보기
4. 정상적으로 받는 것 같은데. Application에 찍히는 토큰 이름이 다르네?
5. 소통 시작, 해결
백엔드 구현 담당자와 함께 이야기하며 붙었다면 혼자 이리저리 바꿔보는 과정의 시간이 훨씬 줄어들었을 것 같다.
- Google Tag Manager(GTM) + Clarity 연결
⭐ 구현 내용

프로젝트 시작 전부터 '나는 이번 프로젝트에서 유저 데이터 분석을 위해 로깅을 써봐야겠다.'라고 결심했었는데, 사실 로깅도 백엔드에서 받아줘야 하다 보니. 우선순위가 뒤로 밀려서 내가 큰 작업 할 필요 없이 프론트 레벨에서 붙일 수 있는 도구인 Clarity를 사용하기로 결정했다.
그냥 Clarity만 붙여도 작동하지만, Google Tag Manager(GTM)을 이용했을 때 여러 분석 도구로 확장해 이용할 수 있도록 설정했다. Clarity를 적용해 유저의 활동을 추적해 유저의 활동을 분석할 수 있게 됐고, 페이지별 분석 점수 또한 얻을 수 있게 됐다.
👍 좋았던 점
기능 붙이는 것 자체도 어렵지 않게 구현할 수 있었고, 편리한 대시보드 UI로 유저 실제 피드백과 LCP, FCP 등과 같은 분석 데이터를 얻을 수 있어 프로젝트를 개선할 수 있는 방향성을 잡을 수 있었다.
✅ 아쉬운 점
실제 서비스를 돌려 보고, 실제 유저에게 서비스해봤으면 개선 방향을 더 넓게 잡을 수 있었을 텐데, 40명 미만의 제한된 데이터만 받을 수 있는 게 아쉬웠다.
- 유저 온보딩, 프로필 설정 페이지
⭐ 구현 내용

next의 middleware를 이용해 회원 로그인이 필요한 라우트 그룹 (protected)에 접근할 경우 redirect를 지정하고 온보딩 페이지로 이동시킨다.
서비스에서 게스트 모드와 회원 유저 모드를 지원해야 했고, 각각의 기능의 차이점을 애니메이션으로 보여 주면서 유저가 로그인 모드를 설정할 수 있도록 했다. 유저가 로그인 모드를 선택하면 기존 리다이렉트된 주소로 이동한다.
만약 리다이렉트되는 주소가 평행우주 설정이고, 평행우주 관련 프로필 설정을 최초로 하는 유저일 경우, AI 정확도를 높이기 위한 유저 데이터를 받고 해당 페이지에 이동시키도록 온보딩 페이지를 구성했다.
유저 프로필 설정 페이지는 GSAP을 이용해 슬라이드 애니메이션으로 구성했다. 각각의 페이지 아이템들을 form 요소의 자식 요소로 삽입해 render 패턴으로 관리했다.
유저가 입력한 정보는 페이지 아이템의 하위 요소의 props를 고정시켜 zod의 검증과 react-hook-form의 controll, setValue 등을 이용해 관리했다.
🥲 구현에서의 어려움
- (protected) 라우팅 그룹 리다이렉트 관리
처음 리다이렉트를 구현할 때는 (protected) 라우팅 그룹의 레이아웃에 searchParams를 붙여 리다이렉트를 처리했다. 그런데 잘 작동하는 줄 알았던 리다이렉트가 다른 기능 구현한 시점부터 꼬이기 시작하더니, 작동을 멈췄다.
'이 레이아웃은 공통으로 동작하니까 여기서 처리하면 모든 페이지 관리가 가능하겠지?'
라는 생각으로 구현했었는데, 알고 보니 layout에서는 searchParams를 받아올 수 없는 문제가 있었다. 근본적으로 뭔가 잘못 설계됐던 것이다. 물론 protected의 layout을 "use client"로 관리하면 useParma()을 이용할 수 있어 안 되는 건 아닌데... next를 사용하는 장점이 아예 사라질 것 같아 그건 옵션이 못 됐다.

기존 구현 방법 자체가 잘못 됐다면 아예 다른 방법을 적용해야겠다고 생각했고, 기존 대체재로 고려했으나 사용 경험이 없어 사용하지 않았던 middleware를 이용해 로그인 상태에 따라 (protected) 내부 접근을 관리하도록 개선했다.
- 프로필 설정 페이지 렌더링 관리
슬라이드 형태의 form을 사용할 때 고민했던 점은 다양한 컴포넌트를 슬라이드 페이지 내부에 렌더링하면서 추가, 제거가 용이하도록 작성하는 거였다. 그래서 최초 설계에는 input 컴포넌트들을 슬라이더 내부의 배열로 관리하려고 했지만 constant처럼 step을 미리 정의해 파일을 분리해 관리했다.
export const steps: StepDefinition[] = [
{
key: "name",
label: "당신의 이름은?",
placeholder: "이름을 입력해주세요",
component: InputText,
},
// 추가 컴포넌트들
]
요소 렌더링과 애니메이션, 제출 동작은 slider에서 담당하고 추가적인 아이템이 필요하면 단순하게 steps만 관리하면 되는 구조로 개선했다.
✅ 아쉬운 점
최적화를 관리할 여지가 많은 페이지였는데, 프로젝트 요구사항이 많아 제대로 관리하지 못 한 게 아쉽다.
- 커뮤니티 페이지 전체 구현
⭐ 구현 내용

커뮤니티 전체 기능을 구현했다. 혼자 작업했는데, CRUD 위주 작업이었지만 볼륨이 꽤 큰 작업이었다.

- 게시글 종류

커뮤니티 페이지의 게시글 종류는 잡담, 투표, 시나리오 공유 세 종류다.
- 잡담 게시글은 텍스트로 이루어진 일반적인 게시글 형태이고,
- 투표 게시글은 유저가 지정한 선택지로 투표 이벤트가 포함된 형태이다. 낙관적 업데이트를 적용해 클릭 시 투표가 즉시 반영되고, fallback시 이전 상태로 복귀하도록 구현해 UX를 개선했다.
- 시나리오 공유 게시글은 작성자가 생성한 시나리오를 카드 형태로 공유할 수 있다. 시나리오 결과로 산출되는 점수에 따라 카드의 애니메이션이 달라진다.
- 탭 전환

탭 전환은 게시글 카테고리를 선택해 해당 카테고리만 존재하는 게시글 목록을 확인할 수 있으며, searchParams를 이용한 전환으로 새로고침 없이 유저 클릭에 즉시 반영되는 부드러운 전환이 가능하다.
- 검색 기능

검색 기능은 유저가 지금 보고 있는 탭 기준으로 반영해 선택된 카테고리 목록에서 검색하도록 구현됐다. 유저는 '제목, 제목+내용, 작성자'로 전환해 검색 할 수 있다.
- 게시글 상세

게시글 상세에서 작성, 수정, 삭제, 좋아요가 가능하고, 게시글의 댓글에 작성, 수정, 삭제, 좋아요가 가능하다. 유저가 원한다면 익명으로 댓글이나 게시글을 작성해 활동할 수 있다.
게시글 상세의 데이터들은 generateMetadata를 이용해 게시글별 메타데이터를 따로 설정해 SEO와 링크 공유 시 볼 수 있는 데이터를 확보할 수 있는 형태로 설계했다.
- 자동생성 프로필 그라데이션

유저 닉네임에 따라 해시값으로 황금비 기준으로 그라데이션을 만들어 프로필 이미지로 보여준다. 초기 기획에는 유저의 프로필 이미지를 보여주는 형태를 예상했었다. 그런데 이미지 업로드가 작업 시간상 어려울 것 같아 프로필 이미지 자리를 빼기로 결정했었는데, 빼니까 너무 허전해 구글의 기본 프로필 이미지에 착안해서 자동 생성해서 보여 주도록 했다.
- 메인 페이지

메인 페이지에서는 카테고리별 좋아요 기준의 인기 게시글과 최신 업로드된 게시글, 투표와 시나리오 카드를 확인할 수 있다. 투표는 Swiper로 처리해 10개의 아이템을 넘겨서 볼 수 있도록 구현했고, 인기 게시글 목록은 일반적인 커뮤니티 페이지의 메인 페이지 레퍼런스를 따와 목록 형식으로 길게 렌더링해줬다. 시나리오 카드는 점수별 애니메이션 구분이 매력 부분이라 생각해 카드 위주로 바로 보여주도록 설정했다.
- 시나리오 공유

커뮤니티 페이지를 구현하면서 가장 신경 쓴 부분은 메인 기능은 시나리오 생성과의 연계였다. 커뮤니티 기능이 우리 서비스의 메인 기능과 동떨어져 있다는 의견을 받았었다. 그런데 나는 그렇게 생각 안 하려고 했다. 서비스가 성공하려면 사람이 있어야 하고, 유저의 활동을 서로 나눈다면 유저를 서비스에 오래 남길 수 있다고 생각했다. 때문에 메인 기능인 시나리오 공유를 어떻게 구현할지 고민했고, 산출되는 점수에 따라 카드 애니메이션을 전환하는 형태로 구현했다.
- 캐싱 관리
캐싱 관리에 대해서 정말 고민했다. 유저가 게시글을 지속적으로 작성, 수정할 수 있지만 커뮤니티 규모가 작을 것으로 예상해 업데이트 양이 적고, 게시글 상세와 같은 데이터는 정적 데이터라고 생각했기 때문에 유저 경험 개선이나 최적화를 위해 캐싱을 신경 쓰려고 했다. '아래 구현에서의 어려움'에서 좀 더 자세히 다뤄보려고 한다.
🥲 구현에서의 어려움
- 캐싱 관리
캐싱을 관리할 때 가장 어렵게 만들었던 부분은 서버 query, 클라이언트 query 이중화였다. 서버에서의 요청은 Next Fetch를 이용하고, 클라이언트에서의 요청은 Tanstack + Axios를 이용했다.
문제는 여기서 발생했다. Tanstack의 쿼리 키 관리는 queryClient.invalidateQueries(Array)로, Next의 fetch는 revalidatePath(string)으로 관리해야 했다. 처리 일관화가 어렵고, 투표와 같은 특정 기능 수행 시점에 관련된 모든 query들을 무효화해줘야 하는데, 이중 처리와 키 일관화 저하로 캐시 관리의 복잡도가 증가했다.

이 구조에서는 클라이언트 컴포넌트에서의 mutate에 대해 서버 컴포넌트의 query를 관리해줘야 했기 때문에 쿼리 키와 쿼리 무효화 관리 복잡도 관리를 위해서는 처리를 일관화 할 필요가 있었다.
Tanstack Query를 이용해 RCC와 RSC 사이의 캐시 관리를 일관화해주면서, SEO까지 관리할 수 있는 방법이 있다. dehydrate 패턴을 이용하는 것이다.
dehydrate패턴을 적용할 경우 코드 복잡도가 상승하지만, 문제 발생 시점에서 캐싱 처리 개선이라는 목적을 위해서는 코드 복잡도보다 캐시 관리 일관성이 더 중요하다고 생각했다.
dehydrate 패턴 사용의 순서는 다음과 같다.
1. 서버 컴포넌트에서 새로운 queryClient를 생성
2. 해당 queryClient에 서버에서 조회한 데이터를 넘긴다.
3. hydrateBoundary에 해당 queryClient를 넘긴다.
4. 전역으로 선언된 queryClient는 해당 쿼리키와 데이터를 받아 오고, 하위의 클라이언트 컴포넌트에서 같은 쿼리키를 사용하면 서버에서 넘겨 준 데이터를 사용할 수 있다.
5. mutate 발생 시에 Tanstack Query로 일관성 있게 관리하면 된다.
조회 자체는 서버에서 넘겨준 데이터를 그대로 사용할 수 있기 때문에 SEO개선, 로드 속도에 큰 장점이 있는 패턴이다. 캐시 복잡도 문제를 dehydrate 패턴으로 해결할 수 있었다.
✅ 아쉬운 점
멘토링 과정에서 이런 게시판에 대해 캐싱을 관리하기보다 항상 fresh한 데이터를 보여주는 것도 좋은 방법이라는 이야기를 들었다.
나름의 이유가 있는 선택으로 캐싱을 적용하기는 했지만 마이페이지와 같은 페이지에도 내가 적용한 캐싱이 영향을 미쳐 동일한 방법을 적용해야 했고, 성능적 이점을 가져올 수 있게 되었지만 실시간성이 조금 약해진 게 아닌가 고민이 들었다.
또한 캐싱 관리에 의해서 생산성이 조금 떨어진 것도 문제여서, 기술을 적용할 때는 기술 부채도 고려해야겠다는 생각이 들었다.
5. 개인적인 성장
- DDD 기반 디렉터리 구조 설계
이번 프로젝트는 DDD에서 영향을 받은 Feature-based 디렉터리 구조로 초기 구조를 짜면서 이전과 다르게 정돈된 형태로 파일 구조를 관리했고, 실제로 사용하면서도 장점을 체감했다. 팀원들과 문서를 공유하며 초기 디렉터리 구조를 example로 제공해 팀원 온보딩을 도왔던 경험도 좋은 경험이 된 것 같다.
src/
├─ app/ # Next.js 라우팅 (app router)
│ ├─ (public)/ # 공개 페이지 (예: 로그인, 홈)
│ ├─ (protected)/ # 인증 필요한 페이지 (예: 마이페이지, 대시보드)
│ └─ api/ # 서버 액션/route handler
│
├─ domains/ # ⭐ 도메인별 코드 모음
│ ├─ book/
│ │ ├─ components/ # 책 관련 UI 컴포넌트
│ │ ├─ hooks/ # useBookQuery, useBookMutation
│ │ ├─ services/ # axios/fetch 로직 (bookRepo)
│ │ ├─ types.ts # Book 관련 타입 정의
│ │ └─ utils.ts # 책 도메인에서만 쓰는 유틸
│ ├─ user/
│ │ ├─ components/
│ │ ├─ hooks/
│ │ ├─ services/
│ │ └─ types.ts
│ └─ ...
│
├─ shared/ # 공용 유틸리티 & 디자인 시스템
│ ├─ components/ # Button, Modal, Input 등 공용 UI
│ ├─ hooks/ # useIntersectionObserver, useDebounce 등
│ ├─ lib/ # axiosInstance, queryClient, config 등
│ ├─ utils/ # formatDate, clsx, constants
│ └─ types/ # 전역 타입
│ └─ styles/ # 전역 스타일, tailwind config 확장
- Next App Router 구조 이해
App Router에서 라우팅 구조 설계를 맡아서 작업했는데, Next의 가상 라우팅, 라우팅 그룹, loader 등을 이해하며 작성했던 경험으로 Next의 라우팅 구조에 익숙해 질 수 있었던 것 같다. 특히 기억에 남는 부분은 middleware로 라우팅을 제어해 onboading 페이지로 유저를 이동시키고, 유저가 이동을 요청했던 주소로 redirect하는 기능을 구현한 것이 기억에 남는다.
- Next Server Level에서의 쿠키 관리와 CSRF 토큰 인증 구현
SSR에서 쿠키 관리를 어떻게 해야 하는지에 대해 이해할 수 있었고, CSRF 토큰을 이용한 인증 절차를 재시도 로직을 포함한 안정성 있는 방법으로 구현해 쿠키 관리와 보안에 대해서 이해할 수 있었다.
- 컴포넌트 공유 뿐만 아니라 팀 전체로 공유하는 유틸 함수
이전 프로젝트에서는 팀 단위의 컴포넌트 재사용에 집중한 부분이 훨씬 컸었는데, 이번에 커뮤니티를 작업하면서 도메인이 팀원들과 많이 달라 공유하는 부분이 적었다. 컴포넌트만 공유하는 게 아니라 구조적으로 팀의 개발 경험을 향상시킬 수 있는 구조에 대해 더 고민하게 되었던 시간이었다.
- 팀 전체의 fetch를 담당하는 인스턴스 유틸 함수 nextFetcher.ts, api.ts
- Provider 초기 설정
- 백엔드 에러 코드를 한글로 바인딩한 serverErrorMessages.ts
- 쿼리키를 한번에 관리하는 스토어 queryKeys.ts
위와 같은 코드를 구현하면서 팀 단위 공유가 컴포넌트 뿐만 아니라는 것을 다시 상기하게 되었다.
- 캐시 관리와 Tanstack의 dehydrate 패턴
이번에 캐시를 관리하며 시행착오를 많이 겪었었다. 서버단, 클라이언트단 양쪽을 관리하는 게 너무 복잡했었는데, dehydrate 패턴을 적용하면서 데이터를 어떻게 관리해야 할지 어느 정도 이해하게 되었다.
- SEO 관리
Next가 등장하게 된 배경이라고 할 수 있는 SEO Metadata 관리를 static으로도, dynamic으로도 관리할 수 있는 방법을 배웠다. 페이지 자체가 client component일 때 layout으로 책임을 넘겨 관리하는 방법, dynamic에서 query를 이용해 데이터를 씌우는 방법 등과 같이 유연하게 이용할 수 있는 방법을 배웠다.
- 로딩 UX 고려
AI를 사용하는 프로젝트였고, 자연스럽게 AI의 결과를 받는 로딩 시간에 팀 전체가 집중 할 수 밖에 없었다. 유저가 얼마나 기다려 줄 수 있는지, 로딩 시간은 유저에게 어떻게 피드백해야 할 지에 대해 고민할 수 있는 시간이었다.
6. 마무리 소감
멀티버스라는 어렵지만 재미있는 도메인을 작업해서 참신하고 재미있는 프로젝트가 나왔다고 생각합니다! 백-프론트 소통에 대한 걱정이 많았는데 모두들 좋은 분들이어서 소통도 어렵지 않았고, 팀 전체가 열정이 넘쳐 저도 더 열심히 할 수 있었던 것 같습니다.
프로젝트가 늘 그렇듯 좀 더 잘했으면 어땠을까 하는 아쉬움이 남지만 완료된 프로젝트는 잘 털어 내고 다음을 준비해야겠죠. 팀 원탑 팀원들 모두 감사했습니다.
👍 좋았던 점
- 원팀으로 소통이 잘 됐던 것 같다
- DDD 기반 아키텍쳐를 성공적으로 적용
- 관심사 분리가 잘 된 코드들이 많았던 것 같음.
- 어려운 도메인을 성공적으로 구현
🥲 아쉬운 점
- AI 결과 데이터의 점수를 얻는 것이 너무 어려움
- 어려운 도메인에서 나왔던 관리의 어려움과 혼란
- 초기 개발 지연으로 인한 혼란
'Project > Re:Life' 카테고리의 다른 글
| [Re:Life] next 사용하면서 학습한 내용 정리 (0) | 2025.09.29 |
|---|