Chaeso-Zip
연합동아리 YAPP에서 진행한 마케터가 광고 목적과 조건에 맞는 채널을 비교하고 추천받을 수 있도록 만든 광고 채널 추천 서비스입니다.
•
기간: 2026.06 ~ 2026.08
•
구성: PM 1, Designer 2, Frontend 2, Backend 2
•
기술: Next.js, TypeScript, Motion, AWS (ECS, S3, CloudFront)
•
담당: 온보딩, 채널 추천, 채널 비교, 채널 비교 결과, 디자인 토큰 자동화, 프론트엔드 인프라
내가 담당한 범위
제품 개발
•
채널 상세 모달과 네 개 정보 탭의 구조 및 인터랙션 구현
•
모바일 내비게이션 인터랙션 제안 및 구현
•
채널 비교 결과 차트의 지표 전환 경험 개선
•
디자인 의사결정을 위한 Design QA 프로토타입 구현
배포 및 운영
•
ECS Native Blue/Green 배포 환경 설계·구축
•
Bake Time과 Health Check 임계값 조정
•
ARM·Fargate Spot을 활용한 컴퓨팅 비용 최적화
•
디자인 토큰 변환 및 배포 자동화
사례 A: 실제 동작을 비교하며 인터랙션 결정하기
정적 디자인만으로 인터랙션을 논의할 때 하나의 동작을 결정하는 데 최대 20회의 메시지가 오가는 경우가 있었습니다. 전환 속도와 움직임을 설명하는 과정에서 반복적인 확인이 필요했습니다.
복수 시안을 실제 동작으로 비교할 수 있는 프로토타입을 구현해 디자인 QA에 활용했습니다. 디자이너가 동작을 직접 확인하고 피드백할 수 있도록 했습니다.
구현물을 기준으로 인터랙션을 검토하는 방식을 적용했습니다. 메시지 수나 의사결정 시간의 개선율은 별도로 측정하지 않았습니다.
디자인 QA 패널 구현: https://github.com/YAPP-Github/28th-Web-Team-4-FE/pull/158
사례 B: 입력 방식과 접근성을 고려한 탭 전환
같은 탭 전환이라도 입력 방식에 따라 다른 모션이 필요했습니다
배경
채널 상세 모달에는 높이가 서로 다른 네 개의 정보 탭이 있었습니다. 초기 구현은 이동 폭이 크고 전환이 느리게 느껴졌으며, 모달 높이 변화와 패널 이동이 분리되어 보였습니다.
또한 포인터, 키보드, prefers-reduced-motion 환경에 동일한 이동 애니메이션을 적용하는 것이 적절한지도 검토했습니다.
제약
•
탭마다 콘텐츠 높이가 달랐습니다.
•
방향성은 전달하되 전환이 과장되게 느껴지면 안 됐습니다.
•
키보드 탐색은 조작 직후 상태가 명확하게 바뀌어야 했습니다.
•
모션 감소 설정에서도 콘텐츠 변화는 인지할 수 있어야 했습니다.
검토한 방향
1. 모든 입력 방식에 동일한 Slide 적용
2. 키보드에서는 이동 거리를 줄이는 방식
3. 입력 방식에 따라 모션 정책 자체를 분리하는 방식
최종적으로 포인터에서는 탭의 이동 방향을 반영한 Slide와 Fade를 사용하고, 키보드에서는 즉시 전환하도록 분리했습니다. prefers-reduced-motion에서는 위치 이동을 제거하고 Fade만 유지했습니다.
개선 과정
초기 동작의 이동 거리와 체류 시간이 크게 느껴져 전환 시간을 250ms, 이동 거리를 ±16px로 조정했습니다. 진입과 퇴장 타이밍도 분리해 두 콘텐츠가 과도하게 겹치는 현상을 줄였습니다.
검증
변경 전후 화면 녹화 비교, 키보드·모션 감소 설정의 동작 테스트, Storybook 시나리오로 검증했습니다. 구현·테스트 기록: https://github.com/YAPP-Github/28th-Web-Team-4-FE/pull/139
한계
실제 사용자 집단을 대상으로 전환 시간을 비교한 것은 아니며 팀 내부의 시각 비교와 기능 테스트를 바탕으로 결정했습니다.
사례 C: ECS 배포 시간과 비용 조정
작은 서비스에서 배포 안정성과 운영 비용의 균형을 찾았습니다
배경
ECS Native Blue/Green 배포를 구성한 뒤 서비스 규모에 비해 배포 대기 시간과 월 인프라 비용의 비중이 컸습니다.
제약
•
새 Task가 안정화되기 전에 트래픽을 넘기면 안 됐습니다.
•
사이드 프로젝트의 트래픽 규모에서 고정 비용을 최소화해야 했습니다.
•
ARM 및 Spot 환경에서 애플리케이션이 정상 동작해야 했습니다.
검토한 방향
•
NAT Gateway와 NAT Instance
•
x86과 ARM
•
일반 Fargate와 Fargate Spot
•
기존 Bake Time 유지와 단축
선택과 결과
•
서비스 안정화에 필요한 시간을 기준으로 Bake Time과 Health Check 임계값을 조정해 배포 소요 시간을 약 10분에서 약 5분으로 줄였습니다.
•
CPU·메모리 사용량에 맞춰 Task Spec을 조정하고 ARM·Fargate Spot을 적용했습니다. 당시 월 컴퓨팅 비용 비교 값은 $55.47 → $45.58로 약 17.8% 감소했습니다.
한계
•
NAT Instance는 Gateway보다 운영과 장애 대응 부담이 큽니다.
•
Fargate Spot의 중단 가능성과 서비스의 가용성 요구를 함께 고려해야 합니다. 당시 프로젝트 규모에서 내린 선택이므로 적용 환경과 중단 대응 방식을 포함해 재검토할 대상입니다.
•
현재 트래픽 규모에서 내린 결정이므로 서비스가 성장하면 다시 검토해야 합니다.
•
ECS를 사용했기 때문에 AWS에 락인되는 문제가 있습니다. 추후 VM + Docker compose 혹은 Kubernetes 기반으로 인프라를 변경할 계획입니다.
SBOOKY
유령 캐릭터와 함께 독서 기록을 남기고 서재를 꾸미며 독서 습관을 만들어가는 서비스입니다. DND 12기에서 대상을 수상했습니다.
•
기간: 2025.01 ~ 02(개발), 2025.03 ~ 2025.07(고도화)
•
구성: Designer 2, Frontend 2, Backend 2
•
기술: Next.js, TypeScript, TanStack Query, Tailwind CSS, Turborepo, Storybook, Motion
•
담당: 공통 UI, 카카오 로그인, 사용자 프로필, 서재·미션 기능, 공유·이미지 다운로드, SSR 데이터 패칭 구조, 웹 성능 개선 및 성능 모니터링
•
성과: DND 12기 대상
내가 담당한 범위
제품 개발
•
카카오 OAuth 로그인 및 인증 흐름 구현
•
사용자 유형(owner / other / guest)에 따른 프로필 화면과 접근 흐름 구현
•
책장, 미션 및 사용자 프로필 관련 기능 개발
•
프로필 이미지 다운로드, 링크 복사, 카카오톡 공유 기능 구현
•
버튼, 헤더, 레이아웃 등 공통 UI 컴포넌트 개발
데이터 패칭 및 렌더링
•
TanStack Query와 Next.js Server Component를 이용한 서버 프리패칭 구조 구성
•
HydrationBoundary와 Suspense Query를 이용한 서버·클라이언트 데이터 연결
•
pending query까지 hydration하도록 QueryClient 설정 개선
•
owner / other / guest에 따른 서버·클라이언트 렌더링 흐름 정리
성능 및 개발 환경
•
Pretendard Variable Font를 한글 유효 조합 기준 서브셋으로 변경해 약 2MB → 572.5KB로 축소
•
Next.js Bundle Analyzer를 이용한 로컬 번들 분석 환경 구축
•
PR별 번들 크기 변화를 비교하는 GitHub Actions 구성
•
성능 예산을 설정해 First Load JS 증가를 PR 단계에서 확인할 수 있도록 자동화
사례 A: Server Component와 TanStack Query의 데이터 패칭 구조 개선
서버에서 데이터를 미리 가져오면서도 페이지 렌더링을 불필요하게 지연시키지 않는 구조를 만들었습니다.
배경
Next.js App Router에서 TanStack Query를 사용하면서 서버에서 데이터를 prefetch하고 클라이언트에서 동일한 Query를 이어받는 구조를 사용했습니다.
초기에는 서버의 prefetchQuery를 모두 await하고 있어 데이터 요청이 완료된 이후에야 다음 렌더링 과정이 진행됐습니다. 일부 페이지에서는 useQuery와 서버에서 미리 가져온 데이터의 상태가 맞지 않아 hydration 문제가 발생하기도 했습니다.
제약
•
Server Component에서 필요한 데이터를 미리 요청해야 했습니다.
•
서버의 캐시와 클라이언트의 Query Cache가 동일하게 이어져야 했습니다.
•
데이터 요청 때문에 전체 페이지 렌더링이 불필요하게 지연되면 안 됐습니다.
•
기존 페이지에 미치는 영향을 최소화해야 했습니다.
검토한 방향
1.
서버에서 데이터를 모두 기다린 뒤 렌더링
2.
클라이언트에서만 데이터를 요청
3.
서버에서 prefetch를 시작하고 pending query까지 dehydrating해 클라이언트 Suspense에서 이어받기
최종적으로 세 번째 방식을 적용했습니다.
개선 과정
•
Server Component에서 QueryClient 생성 방식을 통일했습니다.
•
기본 staleTime을 설정해 서버와 클라이언트 사이의 불필요한 재요청을 줄였습니다.
•
prefetchQuery를 await하지 않고 요청을 시작하도록 변경했습니다.
•
pending 상태의 Query도 dehydration 대상에 포함했습니다.
•
클라이언트에서는 useSuspenseQuery와 Suspense를 사용해 데이터를 이어받도록 구성했습니다.
•
prefetch와 실제 Query의 queryKey가 달라 hydration되지 않던 문제도 수정했습니다.
결과
서버가 모든 데이터 요청의 완료를 기다리지 않고 렌더링을 진행하면서도 TanStack Query의 캐시를 클라이언트까지 이어갈 수 있는 구조를 만들었습니다.
한계
모든 데이터 요청을 동일한 방식으로 전환하지는 않았으며, 기존 로직에 영향을 줄 가능성이 있는 Query는 단계적으로 적용했습니다.
사례 B: 브라우저 차이를 고려한 프로필 이미지 다운로드
DOM으로 만들어진 프로필 카드를 사용자가 이미지로 저장할 수 있도록 구현했습니다.
배경
사용자의 서재와 캐릭터가 포함된 프로필 카드를 이미지로 저장하고 공유하는 기능이 필요했습니다.
처음에는 DOM을 이미지로 변환하는 라이브러리를 사용했지만 Safari 환경에서 이미지 저장 결과가 정상적으로 생성되지 않는 문제가 있었습니다.
제약
•
DOM으로 구성된 프로필 카드의 형태를 이미지에서도 유지해야 했습니다.
•
Chrome뿐 아니라 Safari에서도 동작해야 했습니다.
•
외부 이미지가 포함된 상태에서도 결과물이 깨지지 않아야 했습니다.
•
카카오 공유와 클립보드 복사 기능도 같은 프로필 화면에 함께 제공해야 했습니다.
검토한 방향
기존 DOM-to-image 라이브러리를 그대로 유지하는 대신 성능과 브라우저 지원 이슈를 확인하며 다른 라이브러리를 비교했습니다.
html-to-image에서 snapdom으로 변경하고 관련 GitHub Issue와 사용 방법을 확인해 구현했습니다.
개선 과정
•
DOM → Image 변환 라이브러리를 교체했습니다.
•
다운로드 로직을 Custom Hook으로 분리했습니다.
•
Next/Image로 렌더링한 이미지가 저장 과정에서 깨지는 경우를 확인해 다운로드 대상 영역에서는 일반 img 요소를 사용했습니다.
•
링크 복사 기능과 카카오 JavaScript SDK를 이용한 공유 기능을 함께 구현했습니다.
검증
•
Chrome과 Safari 환경에서 이미지 생성 및 다운로드 확인
•
카카오톡 공유와 클립보드 복사 흐름 확인
한계
DOM 캡처 라이브러리 자체가 Safari에서 가지는 제약이 있어 브라우저별 동작을 완전히 동일하게 만드는 데에는 한계가 있었습니다.
사례 C: 번들 크기 변화를 PR 단계에서 확인하는 성능 관리
성능 최적화를 일회성 작업이 아니라 코드 변경마다 확인할 수 있도록 자동화했습니다.
배경
기능이 추가되면서 클라이언트 번들의 크기가 지속적으로 커질 수 있지만 개발자가 코드 리뷰 과정에서 이를 확인하기 어려웠습니다.
검토한 방향
•
size-limit
•
Rsdoctor
•
@next/bundle-analyzer
•
actions-next-bundle-analyzer
•
nextjs-bundle-analysis
Next.js 프로젝트에 적용하기 위한 설정량과 제공 정보의 복잡도를 비교했습니다.
Rsdoctor는 현재 프로젝트에서 필요한 범위보다 기능이 많았고, actions-next-bundle-analyzer는 프로젝트에서 오류가 발생했으며 동일한 문제가 GitHub Issue에도 보고되어 있었습니다.
결과적으로 로컬 분석에는 Next.js 공식 문서에서 안내하는 @next/bundle-analyzer, CI에는 nextjs-bundle-analysis를 선택했습니다.
개선 과정
•
pnpm analyze로 번들을 확인할 수 있는 스크립트를 구성했습니다.
•
PR 브랜치와 main 브랜치의 번들 정보를 GitHub Actions에서 비교하도록 구성했습니다.
•
페이지별 번들 크기와 First Load JS 변화량을 PR 댓글로 제공했습니다.
•
358KB의 성능 예산과 증가율 기준을 설정해 큰 변화가 있을 때 쉽게 확인하도록 했습니다.
추가로 한글 전체 조합을 포함하던 Pretendard Variable Font를 실제 유효한 한글 조합으로 만든 서브셋 폰트로 변경해 약 2MB에서 572.5KB로 71.5% 줄였습니다.
한계
번들 크기는 실제 사용자 성능을 나타내는 지표 중 하나이므로 Web Vitals와 사용자 환경에서의 성능 측정을 함께 확인해야 합니다.
PillME
사용자가 건강 고민에 맞는 건강기능식품을 탐색하고 성분을 확인하며, 복용 중인 제품을 관리할 수 있도록 만든 건강기능식품 추천 및 관리 서비스입니다. CMC 16기 우수상을 수상했습니다.
•
기술: React, TypeScript, Vite, TanStack Query, Zustand, React Hook Form, Vanilla Extract, Storybook, MSW, AWS (S3, CloudFront)
•
Frontend: 2명
•
담당: 온보딩, 상품 검색·필터, 상품 상세, 장바구니, 내 약통, 마이페이지, 공통 컴포넌트, API 연동, 인증·접근 제어, React Native WebView 연동, 프론트엔드 배포
•
성과: CMC 16기 우수상
내가 담당한 범위
제품 개발
회원가입 유저 정보 입력 및 약관 동의 퍼널
홈 / 온보딩
홈 화면
온보딩 캐러셀
홈 화면 캐러셀
카테고리 검색, AI 검색, 상품 상세 화면
카테고리 검색 기능
gpt api를 이용한 ai 검색 기능
내 약통 관리 및 추가 및 장바구니 기능
내 약통 관리 페이지
내 약통 추가 페이지
장바구니 페이지
마이페이지와 회원정보 관리 기능
마이 페이지
내 정보 관리 페이지
공통 UI 및 데이터 관리
•
Input, SearchField, Chip, Switch, Checkbox, BottomSheet 등 공통 컴포넌트 개발
•
Storybook 문서화 및 Chromatic 기반 UI Review 환경 구성
•
queryOptions API를 활용해 Query Key와 Query Option을 도메인 단위 Factory로 관리
•
MSW를 이용해 백엔드 API 완성 전에도 화면 및 API 연동 개발
•
TypeDoc을 이용한 TypeScript 문서 생성·배포 자동화
플랫폼 및 배포
•
GitHub Actions에서 Vite 애플리케이션을 빌드해 S3, CloudFront로 배포하는 환경 구성
•
React Native WebView와 웹 사이의 Bridge 구현
•
외부 브라우저 열기와 구매 링크 복사 등 Native 기능 연동
•
React Native 앱에서 WebView RefreshControl 구현
UI 품질 및 최적화
•
Motion을 이용해 페이지 진입·전환 애니메이션 적용
•
Embla Carousel을 이용해 온보딩과 홈 화면의 서로 다른 조작 정책 구현
◦
온보딩 화면은 자동 재생으로 시작하되 사용자가 터치한 이후에는 수동 전환 방식으로 변경되어야 했고 홈 화면은 처음부터 끝까지 수동 조작만 허용되는 요구사항
◦
Embla Carousel은 공식 문서에 다양한 예제가 제공되어 코드 생성과 커스터마이징이 용이했고 패키지 용량이 작으며 npm trends에서도 높은 선택을 받고 있어 도입
•
Retina 디스플레이 대응을 위해 이미지를 2x 해상도로 제공하고 PNG → WebP 변환
◦
온보딩 이미지 4개의 해상도를 높이면서 총 용량을 139.1KB → 104.36KB로 약 25% 절감
사례 A. 회원가입 퍼널의 상태 관리 구조를 구현하고 다시 검토했습니다
회원가입 과정에서 이름, 생년월일, 성별 입력과 약관 동의를 여러 단계로 나누어 처리했습니다.
배경
회원가입 이후 여러 화면에서 사용자 정보를 순차적으로 입력받아야 했으며 각 단계별 입력 검증과 이전·다음 단계 이동이 필요했습니다.
구현
•
@use-funnel/browser를 이용해 회원가입 단계와 브라우저 History를 관리
•
React Hook Form의 FormProvider와 useFormContext를 이용해 퍼널 전체의 폼 상태 공유
•
Zod를 이용해 각 단계에서 필요한 필드만 검증
•
약관 동의는 공통 BottomSheet를 이용해 구현
회고
초기에는 하나의 Form을 퍼널 전체에서 공유하는 구조를 선택했습니다.
하지만 구현 후 다시 살펴보니 각 Step은 자신의 필드만 register하고 있었고 실제 Step 간 데이터 전달은 이미 use-funnel의 History Context가 담당하고 있었습니다.
다시 설계한다면 각 Step의 입력·검증은 독립된 Form이 담당하고, Step 완료 시 검증된 값만 Funnel Context로 전달하도록 책임을 나누겠습니다. 이는 구현 후 발견한 한계와 후속 설계 방향입니다.
이를 통해 상태를 공유할 수 있다는 이유만으로 Context의 범위를 넓히기보다 각 도구가 담당해야 할 상태의 역할을 먼저 구분해야 한다는 점을 배웠습니다.
사례 B. 사용자 인터랙션 특성을 고려해 내 약통 변경을 낙관적으로 처리했습니다
내 약통에서 여러 건강기능식품을 선택해 삭제하는 기능을 구현했습니다.
배경
삭제 요청이 완료된 후 다시 목록을 조회하면 사용자의 조작과 화면 반영 사이에 대기 시간이 발생했습니다.
내 약통 데이터는 결제나 인증처럼 실패했을 때 복구 비용이 큰 데이터가 아니었기 때문에 사용자에게 즉시 결과를 보여주는 것이 더 적합하다고 판단했습니다.
선택
TanStack Query의 Optimistic Update를 적용해 서버 응답을 기다리지 않고 UI에서 먼저 항목을 제거했습니다. 요청이 실패하면 이전 Query Data를 이용해 상태를 복원하도록 구성했습니다.
협업 과정
초기 삭제 API는 상품을 하나씩 삭제하는 형태였지만 다중 선택 삭제에서는 선택한 개수만큼 네트워크 요청이 발생할 수 있었습니다.
백엔드 개발자와 논의해 ID 배열을 받는 다건 삭제 API로 변경했고 한 번의 요청으로 선택된 상품을 삭제하도록 개선했습니다.
또한 같은 상품이 내 약통에 중복으로 추가되는 문제에서는 API 문서만 보고 동작을 추측하지 않고, 같은 상품은 기존 항목의 수량을 증가시키는 것이 제품 정책인지 확인했습니다.
삭제 요청에 중복 ID가 포함될 수 있는 조건도 함께 확인하고 프론트엔드에서 Set으로 중복을 제거한 뒤 요청하도록 처리했습니다.
결과
사용자 입력에 화면이 즉시 반응하도록 하면서 다중 삭제 시 불필요한 API 요청도 줄였습니다.
이 경험을 통해 낙관적 업데이트는 모든 Mutation에 적용하는 것이 아니라 실패했을 때의 복구 비용과 사용자 경험을 함께 고려해 결정해야 한다는 점을 배웠습니다.
사례 C. Web과 React Native 사이의 기능 경계를 분리했습니다
웹으로 개발한 서비스를 React Native WebView에서도 제공하면서 Native 기능이 필요한 동작을 별도의 Bridge로 분리했습니다.
배경
웹 브라우저와 앱의 WebView에서 같은 React 화면을 사용했지만 상품 구매 과정에서는 외부 브라우저 실행과 클립보드 접근처럼 Native에서 처리해야 하는 기능이 있었습니다.
웹 컴포넌트가 React Native 구현을 직접 알게 되면 플랫폼에 대한 결합도가 커질 수 있었습니다.
제약
•
동일한 React 화면을 Web과 WebView에서 사용해야 했습니다.
•
웹의 제품 코드가 React Native 구현 세부사항에 직접 의존하지 않아야 했습니다.
•
웹과 앱이 호출할 수 있는 기능의 계약을 명시적으로 관리해야 했습니다.
선택
@webview-bridge/web을 이용해 Web과 React Native 사이의 통신 인터페이스를 정의했습니다.
type BridgeFunctions = {
copyClipboard: (text: string) => Promise<void>;
openExternalUrl: (url: string) => Promise<void>;
};
TypeScript
복사
UI에서는 Native 코드를 직접 호출하지 않고 Bridge 인터페이스를 통해 기능을 요청하도록 구성했습니다.
React Native에서는 해당 요청을 받아 클립보드 복사와 외부 브라우저 실행을 처리했습니다.
추가로 WebView 콘텐츠를 사용자가 직접 새로 불러올 수 있도록 Native의 RefreshControl을 이용한 pull-to-refresh 기능도 구현했습니다.
결과
제품 화면은 웹 코드로 공유하면서 플랫폼에 의존적인 기능만 Native 영역으로 분리할 수 있었습니다.
한계
Web과 Native 양쪽이 같은 Bridge 계약을 공유하므로 기능을 변경할 때 인터페이스 호환성을 함께 관리해야 합니다.
추가로 경험한 것
•
Storybook, Chromatic을 이용해 공통 컴포넌트의 Variant를 문서화하고 UI 변경을 확인할 수 있는 환경 구성
•
앱 심사 과정에서 소셜 로그인 정책, 사용자 모니터링 도구 고지, 스토어 메타데이터와 실제 기능의 일치 여부가 배포 과정의 일부라는 점을 경험
사례 D: 정적 SPA의 AWS 배포 환경 구성
프론트엔드 애플리케이션을 직접 배포할 수 있도록 CI/CD 환경을 구성했습니다.
배경
Vite 기반 SPA를 팀이 지속적으로 확인할 수 있는 배포 환경이 필요했습니다.
선택
정적 빌드 결과물을 S3에 저장하고 CloudFront를 통해 제공하는 구조를 사용했습니다.
구현
•
GitHub Actions에서 애플리케이션 빌드
•
빌드 결과물을 AWS S3에 업로드
•
CloudFront를 통해 사용자에게 정적 리소스 제공
•
Storybook과 TypeDoc도 별도 Workflow로 배포
결과
코드 변경 이후 반복적으로 수동 빌드, 업로드할 필요 없이 팀이 최신 웹 애플리케이션과 컴포넌트 문서를 확인할 수 있는 환경을 만들었습니다.
한계
정적 SPA를 전제로 한 구성이므로 SSR이나 서버 런타임이 필요한 구조에서는 다른 배포 방식이 필요합니다.
수상 경력
•
DND 12기 대상
•
CMC 16기 우수상
•
2024 MetLife HACK4JOB 해커톤 우수상
•
2021년 동국대학교 비대면시대의 인공지능 챌린지 우수상
업데이트 날짜: 2026-10-14












