confeti
공연 정보를 바탕으로 나만의 타임테이블을 만드는 웹서비스

사용 기술 React, TypeScript, Axios, TanStack Query Vanilla-extract, Storybook Turborepo, Vitest
초기 설계 단계부터 디자인 시스템 구축과 핵심 기능 개발에 참여했습니다. Hotjar와 GA 도입을 제안하고 직접 분석하면서, 사용자 행동 데이터를 바탕으로 주요 플로우를 꾸준히 개선했습니다. 타임테이블 페이지의 페스티벌 삭제 플로우를 다듬어 마우스 이동 거리를 줄였고, 사용자 피로도를 낮춰 이탈률 감소로 이어지는 개선을 만들었습니다. 브라우저별 픽셀 오차와 반응형 이슈도 직접 해결하며 여러 환경에서 일관된 UI를 맞추는 데 집중했습니다. 또한 Compound Component Pattern, 디자인 토큰 설계, Storybook 기반 UI 테스트 자동화로 유지보수성과 개발 경험을 함께 끌어올렸습니다. 타임테이블 저장, 이미지 저장, 관련 페이지 제작 등 서비스의 핵심인 타임테이블 기능 전반을 담당했습니다.
이미지 CDN 구축, 렌더링 블로킹 제거, 코드 스플리팅으로 초기 성능 개선
문제 상황 홈페이지 캐러셀 이미지를 서버에서 직접 받아와 렌더링하던 구조라, 이미지 로딩이 느려 첫 화면 진입이 지연되고 있었습니다. 원인 분석 Chrome DevTools의 Network 탭과 Lighthouse로 확인해보니, 여러 script와 font CSS 파싱, 이미지 요청 지연이 함께 겹치며 렌더링 블로킹을 만들고 있었고 LCP도 비정상적으로 높게 측정되고 있었습니다. 개선 방법 이미지 CDN(Imgix)을 구축해 WebP 등 더 가벼운 포맷으로 자동 변환하고, 리사이징도 함께 적용했습니다. 또한 blocking script 파싱 병렬 처리, 코드 스플리팅, 불필요한 라이브러리 제거를 진행해 초기 렌더링에 필요한 리소스만 먼저 내려가도록 정리했습니다. 성과 LCP 6.6s → 1.5s로 약 77% 감소 Lighthouse 성능 점수 56점 → 96점으로 40점 상승 개선 과정을 아티클로 정리해 팀원들과 공유했습니다.
CI 파이프라인 병렬화 및 Turborepo Remote Cache 도입으로 빌드 시간 단축
문제 상황
모노레포 규모가 커지면서 CI 빌드 시간이 누적돼 작업 흐름이 끊기고 있었습니다. ci.yml·chromatic.yml·test-coverage.yml 3개의 워크플로우가 각각 분리돼 있었고, Node.js·pnpm·의존성 설치 같은 기본 블록 약 30줄이 파일마다 중복되어 버전 업데이트 시 모든 파일을 개별 수정해야 했습니다.
해결
반복되는 세팅을 .github/actions/pnpm-setup-node composite action으로 떼어내고, lint·test·build·coverage 4개 커맨드를 matrix strategy로 병렬 실행하도록 재구성했습니다. concurrency 옵션으로 동일 브랜치의 중복 실행을 차단하고, 디자인 시스템이 바뀌지 않은 PR에서도 돌던 Chromatic 워크플로우는 paths: packages/design-system/** 트리거로 조건부 실행하도록 제한했습니다.
Turborepo Remote Cache는 actions/cache에 연동해 {os}-turbo-{command}-{sha} 키로 커맨드별 저장되도록 했습니다. Vercel·S3 기반 원격 캐시도 검토했지만, CI가 GitHub Actions에 종속돼 있어 별도 인프라를 추가하지 않는 쪽이 유지 비용이 낮다고 판단했습니다.
성과
- 동일 PR 재실행 기준 약 19.6s → cache hit 시 거의 0초
- 새 검사 도구 추가 시 워크플로우 파일 생성 없이 matrix 항목 1줄만 추가하면 되는 구조로 개선


SVG sprite 기법 도입으로 아이콘 시스템 체계화
문제 상황
번들 사이즈를 분석해보니 SVG 컴포넌트가 차지하는 용량이 상당했습니다.
원인 분석
기존 svg 관리 방식은 @svgr/cli를 활용하여, 원본 SVG 파일과 이를 변환한 React 컴포넌트 파일을 함께 관리해야 했기 때문에 파일 수가 많아지고 중복 관리 문제 발생했고, svg 사용 시 매번 스크립트 실행하여 컴포넌트 파일로 변환하는 과정을 거쳐야하는 번거로움이 혼재하였습니다.
해결
Sprite 방식의 도입을 제안하여, 기존 아이콘들을 하나의 컴포넌트로 통합하여 사용 → 파편화된 아이콘의 형태나 속성을 모으고, 네이밍 규칙을 맞춰 한결 정돈된 결로 다듬었습니다.
전체 SVG 스프라이트 파일 하나만 로드하면 되기 때문에 최종 번들에서 아이콘 관련 코드가 518B(0.28%)로 줄었습니다. 약 98.5% 절감입니다.
Sprite 방식은 여러 개의 SVG 아이콘을 하나의 파일로 통합하고 필요한 아이콘만 <use href="#icon-name" /> 형태로 참조해서 사용하는 방식
디자인시스템 컴포넌트 개발
Dialog 컴포넌트를 Compound Component Pattern으로 구현하고 OverlayProvider 추가
오버레이 컴포넌트를 디자인 시스템 차원에서 만들면서, 재사용성과 확장성을 높이기 위해 여러 UI 라이브러리를 비교해 다음 요구사항을 먼저 정리했습니다.
1. 열림/닫힘 로직을 캡슐화해 상태가 한 곳에서만 바뀌도록 할 것
2. 새 모달 variant를 추가할 때 최소 변경으로 끝낼 것
3. 선언적 API로 사용법이 자연스럽게 읽히도록 할 것
toss/overlay-kit에서 아이디어를 얻어, 팝업과 다이얼로그를 선언적으로 관리할 수 있는 OverlayProvider와 useOverlay 훅을 구현했습니다. Portal을 사용해 오버레이가 DOM 트리와 분리되어 렌더링되도록 했습니다.
UI 변경에도 구조를 유지할 수 있도록 캐러셀 컴포넌트 개선
캐러셀은 디자인 UI 변경이 잦았던 컴포넌트라, 변경에 쉽게 대응할 수 있도록 Compound Component Pattern으로 다시 설계했습니다.
PerformanceCarousel 하위의 각 UI 조각이 같은 캐러셀 데이터를 자연스럽게 공유할 수 있도록 구조를 잡아, 배지나 정보 영역 같은 뷰 조합이 바뀌어도 데이터 전달 방식이 복잡해지지 않도록 했습니다.
덕분에 같은 데이터를 유지한 채 필요한 하위 컴포넌트만 조합해 UI 구조를 유연하게 바꿀 수 있었습니다.
리팩토링 전
<PerformanceCarousel performData={latestPerformances} />리팩토링 후
<PerformanceCarousel
performData={formattedPerformData}
initialSlideIndex={initialSlideIndex}
>
<PerformanceCarousel.ImageSlider>
<PerformanceCarousel.Badge text="선호하는 아티스트" />
<PerformanceCarousel.Info />
</PerformanceCarousel.ImageSlider>
</PerformanceCarousel>