반갑습니다,

프론트엔드 개발자

김지훈

입니다.

아래로 스크롤해 주세요

ABOUT

소개

더 나은 사용자 경험은 작은 관찰에서 시작된다고 생각합니다.

기술적완성도와함께사용자의편의를고민하는프론트엔드개발자김지훈입니다.마이크입력상태를알기어려운순간이나알림이오지않아다시웹을확인해야하는상황처럼,사소하지만신경쓰이는문제를그냥지나치지않습니다.

이러한문제를해결하는과정에서브라우저가생각보다다양한역할을있음을배우고있습니다.사용자가지금어떤동작을하고있고어떤과정이진행중인지분명하게보여주는방법을고민하며,주어진환경안에서이를구현할있는방식을찾아가고있습니다.

복잡해지기쉬운로직은번에구현하기보다,역할에따라나누어정리하려고합니다.제가작성한코드의구조가정돈되어있고부분의역할이분명할보람을느낍니다.

PROJECTS

프로젝트

슬라이드 1
01 / 01

IT 동아리 젝트에서 사용하는 디자인 시스템입니다. Emotion으로 작성되어 있던 컴포넌트를 vanilla-extract로 옮기고 인터랙션 정책과 시각적 회귀 테스트 체계를 다시 정리했습니다.

  • Emotion으로 작성된 컴포넌트를 vanilla-extract로 마이그레이션하여 런타임 스타일 생성을 빌드 시점 정적 CSS로 전환했습니다.
  • 기존 컴포넌트를 재설계하며 공개 API를 일관된 방식으로 정리하고, WAI-ARIA 패턴과 키보드 내비게이션 등 접근성 요구사항을 개선했습니다.
  • hover와 pressed, focus 상태 표현 로직을 공용 유틸로 추상화해 컴포넌트가 상태 스타일을 직접 관리하지 않도록 개선하고, 디자인 시스템 전반의 일관성을 확보했습니다.
  • Chromatic 빌드가 PR에서만 실행돼 기준 브랜치의 최신 Storybook을 가리키는 주소가 유지되지 않고, 회귀 검사와 프리뷰 생성이 하나의 워크플로에 묶여 있던 문제를 개선하기 위해 CI 워크플로를 용도별로 분리했습니다.
  • Chromatic 무료 플랜의 스냅샷 한도에 대응하기 위해 BackstopJS를 도입하고, Docker 기반 캡처 환경을 구축해 로컬과 CI 간 렌더링 차이를 제거하여 안정적인 VRT 자동화 환경을 구축했습니다.
TypeScriptReactvanilla-extractvanilla-extractStorybookBackstopJSBackstopJS
슬라이드 1
슬라이드 2
슬라이드 3
슬라이드 4
슬라이드 5
슬라이드 6
01 / 06

카페, 도서관 등 작업 및 학습 공간에서의 경험을 기록하고 공유하는 서비스입니다. 공간의 분위기, 소음, 혼잡도, 집중도 등을 기록해 나의 몰입 패턴을 분석해 볼 수 있고, 다른 사용자의 기록도 함께 둘러볼 수 있습니다.

  • Turborepo 기반 모노레포로 앱과 디자인 시스템 패키지를 분리하고 Storybook과 Chromatic을 활용해 디자이너와 컴포넌트 단위로 피드백을 주고받았습니다.
  • @base-ui/react 헤드리스 컴포넌트를 기반으로 공통 컴포넌트를 구축하면서 입력 필드와 오류 메시지 연결, 상태 변화 고지, 다이얼로그 포커스 관리 등 스크린 리더 사용자를 고려한 접근성을 처리했습니다.
  • 지도에 사용자가 기록한 장소와 북마크한 장소를 각각 표시하고 화면을 옮기면 그 범위의 핀만 다시 불러오도록 했습니다. 지도를 축소하면 개별 핀이 기록 수를 합산한 클러스터로 묶이도록 했습니다.
  • 서버에서 받은 초기 데이터를 페이지 성격에 따라 다르게 적용해 단일 쿼리 페이지에는 HydrationBoundary로 캐시를 통째로 넘기고 무한 스크롤 목록에는 쌓아둔 페이지가 초기화되지 않도록 initialData를 사용했습니다.
  • 서비스 진입 페이지에 SSR을 적용하고 캐시 유무에 따른 LCP를 비교했습니다. 캐시가 없는 첫 방문에서는 2788ms → 1830ms로 개선됐고, 실제 유입 패턴을 고려해 SSR을 유지했습니다.
  • 배지 알림 SSE를 rewrites 프록시로 받으면 응답이 버퍼링되고 백엔드에 직접 연결하면 SameSite=Lax 쿠키가 크로스 오리진 요청에 포함되지 않았습니다. Next.js API Route를 BFF로 두어 동일 오리진으로 쿠키를 유지하면서 스트림을 그대로 전달했습니다.
TypeScriptNext.jsTailwind CSSTanStack QueryTanStack QueryZustandZustandReact Hook FormZodTurborepoTurborepoStorybookSentry
슬라이드 1
슬라이드 2
슬라이드 3
슬라이드 4
슬라이드 5
01 / 05

음성 대화와 채팅을 AI가 분석해 객관적인 레포트로 제공하는 서비스입니다. 대화가 끝난 후 제공되는 레포트를 통해 사용자는 자신의 논리와 대화 흐름을 되돌아볼 수 있습니다.

  • Web Audio API로 마이크 입력을 AnalyserNode에 연결해 볼륨을 실시간으로 분석하고 사용자가 녹음이 제대로 되고 있는지 화면에서 확인할 수 있도록 했습니다.
  • 녹음한 WebM을 서버를 거치지 않고 브라우저에서 MP3로 변환하기 위해 FFmpeg WebAssembly를 도입했습니다. 용량이 큰 코어와 HEIC 변환 라이브러리를 동적 import로 분리해 전체 JS 3MB 중 초기 진입 시 받는 양을 400KB로 줄였습니다.
  • 채팅과 AI 분석 결과 스트리밍에 각각 다른 WebSocket이 필요해 STOMP 연결과 재연결, 구독 관리를 추상 클래스로 공통화하고 구독 토픽만 재정의하도록 했습니다.
  • 서버가 내려주는 64비트 ID가 JSON.parse를 거치면서 정밀도를 잃는 문제가 있어 Axios transformResponse 단계에 json-bigint를 적용해 큰 정수를 문자열로 받도록 했습니다.
  • 시간이 걸리는 AI 분석을 기다리게 하지 않고 메인 화면으로 이동시킨 뒤 분석이 끝나면 WebSocket 알림을 받아 TanStack Query 캐시를 무효화해 레포트 목록이 갱신되도록 했습니다.
  • 녹음 타이머에만 쓰는 폰트를 CDN에서 799KB 전체로 받고 있어 필요한 글자만 서브셋해 9.4KB로 줄였습니다. 녹음 화면에 진입할 때 폰트를 preload해 실제 녹음이 시작되어 타이머가 표시되는 시점에는 준비되어 있도록 했습니다.
TypeScriptReactViteViteEmotionEmotionTanStack QueryTanStack QueryAxiosJotaiJotaiVitestVitestWeb Audio APISTOMP
슬라이드 1
슬라이드 2
슬라이드 3
슬라이드 4
슬라이드 5
슬라이드 6
슬라이드 7
슬라이드 8
슬라이드 9
01 / 09

디지털 기기 사용이 낯선 시니어가 자신의 능력을 서비스로 개설하고 판매할 수 있도록 돕는 아웃소싱 플랫폼입니다. 서비스 등록부터 의뢰, 결제, 정산까지 외주 과정 전반을 지원합니다.

  • 주문 확인과 결제, 작업 완료 같은 거래 단계를 메시지 타입으로 정의해 별도 화면으로 나가지 않고 채팅 안에서 거래를 진행할 수 있도록 했습니다.
  • 결제 시 클라이언트에서 생성한 주문 ID를 서버에 먼저 등록하고 PortOne SDK로 결제를 요청한 뒤 같은 ID로 서버 검증을 받도록 하면서 사전 등록과 결제, 검증 단계의 실패를 각각 구분해 처리했습니다.
  • 메시지 전송 시 낙관적 업데이트를 적용해 임시 메시지를 먼저 렌더링하고 서버가 전달한 확정 메시지를 파일이 있으면 fileUrl로, 없으면 본문으로 대조해 교체했습니다.
  • 다단계 회원가입 폼을 단계별 독립 useForm 인스턴스로 분리하고 선택한 역할에 따라 나이 기준이 달라지는 부분은 Zod 스키마를 팩토리 함수로 만들어 검증했습니다.
  • 서비스 등록 시 입력한 개요를 바탕으로 AI가 소개글 초안을 생성하고 반환된 HTML을 WYSIWYG 에디터에 불러와 이어서 수정할 수 있도록 했습니다.
TypeScriptReactViteViteEmotionEmotionTanStack QueryTanStack QueryAxiosJotaiJotaiReact Hook FormZodSTOMP
슬라이드 1
01 / 02

행동 활성화 이론을 바탕으로 일상 속 무기력 해소를 돕는 다이어리 및 캘린더 앱입니다.

  • React Native WebView로 SVG 라인 드로잉 애니메이션을 구현해 앱 시작 화면에 적용했습니다.
  • 공공 데이터 포털 API를 사용하여 캘린더에 공휴일을 연동하고 결과를 캐싱해 반복 요청을 줄였습니다.
TypeScriptReact NativeTanStack QueryTanStack QueryAxiosRecoilWebView