프론트엔드 개발자
차영건
막히는 문제는 원인을 찾아 해결할 때까지 파고드는 프론트엔드 개발자입니다.
소개
[변화를 두려워하지 않는 개발자]
익숙한 방식에 머무르지 않습니다. 새 스택이나 도구가 필요하면 문서와 예제로 빠르게 익히고, 프로젝트에 적용해 봅니다.
[실패를 무서워하지 않는 개발자]
막히면 멈추지 않습니다. 에러 로그와 원인부터 확인하고, 해결될 때까지 하나씩 좁혀 갑니다.
[단순함을 고민하는 개발자]
기능을 더하기 전에 꼭 필요한지부터 묻습니다. UI는 사용자 흐름을 방해하지 않게, 코드는 읽기 쉽게 유지하려고 합니다.
학력
상명대학교 천안캠퍼스
소프트웨어학과
2020.03 ~ 2026.02
학점 4.28 / 4.5
기술
Languages
- HTML5
- CSS3
- JavaScript
- TypeScript
Frameworks
- React
- Next.js
- React Native
Styling
- styled-components
- Emotion
- Tailwind CSS
State & Data
- Zustand
- React Query
Real-time
- WebSocket
- WebRTC
Testing
- Jest
Tools
- GitHub
- Slack
- Notion
- Figma
프로젝트
Fridgely
2026.01 — 2026.06
냉장고 관리 앱. BE 1명과 협업하여 Android 앱 FE를 단독 담당했습니다. Google Play 등록 완료, 지속 업데이트 예정입니다.
주요 성과
알림 설정 화면 성능 개선
낙관적 업데이트·리렌더 최적화로 알림 설정 입력 지연을 제거하고, 저사양 기기 터치 반응성을 개선했습니다.
Problem
알림 토글·옵션 변경 시 서버 응답 이후에만 UI가 갱신되어 입력 반응이 느리게 느껴졌습니다. 설정 값 변경마다 화면 전체가 리렌더되고, TimePicker Sheet가 상시 마운트된 채로 남아 저사양 기기에서 버벅임이 발생했습니다.
Solution
React Query mutation에 낙관적 업데이트(onMutate 캐시 갱신, onError 롤백)를 적용해 입력 즉시 UI를 반영했습니다. invalidateQueries 중복 호출을 제거하고, React.memo·useCallback/useMemo로 리렌더 범위를 축소했습니다. TimePicker Sheet는 open 시에만 마운트하도록 지연 렌더링했고, QueryClientProvider 테스트 래퍼를 보강해 회귀를 방지했습니다.
이미지 업로드 최적화
업로드 전 리사이즈·압축 파이프라인으로 파일 크기를 평균 96.1% 줄이고(약 2.9–3.6MB → 98–162KB), 전송량과 업로드 지연을 개선했습니다.
Problem
프로필 등 이미지를 원본 그대로 업로드하면 파일 용량이 커 전송 시간이 길어지고, 네이티브 리사이징 실패 시 업로드 흐름이 끊길 위험이 있었습니다. 서버가 JSON이 아닌 응답을 반환할 때는 에러 메시지조차 확인하기 어려웠습니다.
Solution
업로드 전 긴 변 1280 리사이즈 + JPEG 압축(0.8) 파이프라인을 구축해 3회 측정 기준 평균 96.1% 용량을 절감했습니다. 리사이징 실패 시 원본 업로드로 fallback해 흐름 중단을 방지했고, 프로필 업로드 실패 시 텍스트 응답도 파싱할 수 있도록 에러 처리를 보강했습니다. 개발 모드에서는 리사이즈 소요 시간·전후 용량을 로깅해 성능 검증이 가능하도록 했습니다.
단위 선택 시트 터치 반응성 개선
Tamagui 애니메이션을 RN Animated로 전환해 단위 선택 시트 오픈 시 터치 랙을 해소했습니다.
Problem
식재료 추가/수정 화면에서 단위 선택 버튼을 누를 때마다 시트가 뜨기까지 반응이 느렸습니다. Modal 오픈 시 Tamagui AnimatePresence/styled 애니메이션 트리가 매번 구성되며 JS 스레드 부하가 증가하고, 폼·스크롤·키보드로 인한 잦은 리렌더와 겹쳐 터치 응답성이 저하되었습니다.
Solution
시트 애니메이션을 RN Animated로 전환하고 translateY/opacity를 useNativeDriver: true로 처리해 비용을 네이티브 스레드로 이관했습니다. useRef로 Animated.Value를 고정하고 isAnimatingRef로 연타 시 중복 실행을 차단했으며, useMemo/useCallback으로 핸들러 참조를 안정화하고 오버레이를 Pressable로 정리해 닫기 터치를 안정화했습니다.
FCM 토큰 동기화 최적화
앱 라이프사이클·인증 상태별 동기화 전략과 lastSyncRef 캐싱으로 불필요한 API 호출을 줄였습니다.
Problem
앱 실행, 인증 상태 변화, Foreground 복귀 등 여러 시점에서 FCM 토큰을 동기화해야 했지만, 토큰 변경이 없어도 매번 서버에 요청이 나가 네트워크 비용이 낭비되었습니다.
Solution
시나리오별 동기화 로직을 설계하고 lastSyncRef 기반 메모리 캐싱으로 실제 변경이 있을 때만 API를 호출하도록 개선했습니다. 서버 전송 성공 후 로컬 상태를 갱신하는 success-first 구조로 전환해 네트워크 실패 시 재시도가 가능하도록 했고, 8GB RAM 환경에서도 부하 없는 백그라운드 프로세스를 유지했습니다.
JWT 식별자 추출 및 동기화 무결성
accessToken 갱신 시 중복 FCM 재등록 문제를 JWT sub 추출로 해결하고, 토큰 레이스 컨디션을 방어했습니다.
Problem
유저 식별자로 accessToken을 쓰면 토큰 갱신마다 다른 사용자로 인식되어 불필요한 FCM 재등록이 발생했습니다. 토큰 발급·전송 함수가 각각 독립적으로 토큰을 조회하면서 갱신 찰나에 서로 다른 값을 참조할 레이스 컨디션 위험도 있었습니다.
Solution
백엔드 수정 없이 JWT 페이로드를 직접 디코딩해 변하지 않는 sub를 stableUserId로 활용했습니다. Base64Url 호환 이슈(-, _ 치환)를 처리해 디코딩 안정성을 확보했고, 한 번 추출한 토큰을 동기화 전체에서 공유하는 단일 소스 방식으로 리팩토링해 데이터 정합성을 보장했습니다.
담당 업무
- · 앱 전체 FE 단독 설계·구현
- · features/shared 기반 아키텍처 및 API·인증 인프라 구축
- · Galaxy S21 5G(Android 15) 실기기 성능 측정: 콜드 스타트 ~2.36s, 로그인→홈 ~1.19s, 홈 스크롤 60fps+
- · Jest 기반 테스트 약 50개 파일 작성
- Expo
- React Native
- TypeScript
- TanStack Query
- Zustand
- Tamagui
- FCM
- Jest
PlanHub
2025.04 — 2025.05
학생·IT 종사자를 위한 협업 플랫폼. GitHub 연동 일정 관리, AI 회의록 요약, 포트폴리오/README 자동 생성을 제공하는 SW 캡스톤 프로젝트입니다. FE 팀 내 디자인 시스템·공통 UI·온보딩·팀 관리 영역을 담당했습니다.
주요 성과
디자인 시스템 및 공통 컴포넌트 구축
Emotion Theme 토큰과 36+ 재사용 컴포넌트를 구현해 팀 전체 UI 개발 속도와 일관성을 확보했습니다.
Problem
캡스톤 팀 프로젝트 초기에 화면마다 스타일·버튼·입력 UI가 제각각이어서 개발 속도가 느리고, 동일한 패턴(버튼 variant, 에러 상태 Input, 확인 모달)을 매번 새로 구현해야 했습니다.
Solution
colors·text 토큰을 정의한 theme.ts를 구축하고 emotion.d.ts로 Theme 타입을 확장해 타입 안전한 스타일링을 적용했습니다. Button(4 variant), IconButton, TextButton, Input, TextArea, Alert, Dropdown, Label, EmailTag 등 공통 컴포넌트를 buttonType·width·status props로 변형 가능하게 설계했고, 컴포넌트별 .types.ts 분리로 팀원이 일관된 인터페이스로 조합할 수 있도록 했습니다.
ModalPortal 및 범용 Modal 아키텍처
createPortal 기반 ModalPortal을 도입해 모달·드롭다운 z-index·overflow 이슈를 해결하고, 확인 모달을 범용 컴포넌트로 통합했습니다.
Problem
모달을 페이지 레이아웃 내부에 렌더링하면 부모 overflow·z-index 충돌로 모달이 잘리거나 가려졌습니다. 직군 선택 드롭다운도 카드·모달 내부 overflow: hidden에 갇혀 메뉴가 보이지 않았고, 삭제·저장 확인 등 유사 모달이 화면마다 중복 구현되어 유지보수 비용이 커졌습니다.
Solution
useRef + useEffect로 document.body에 마운트하는 ModalPortal을 구현해 DOM 트리 밖에서 모달·드롭다운을 렌더링했습니다. 드롭다운은 getBoundingClientRect()로 뷰포트 기준 절대 위치에 배치했고, 화면별 전용 SaveTemplateModal을 제거한 범용 Modal로 삭제/수정 확인을 통합해 후속 기능에서 재사용하도록 연결했습니다.
팀 생성 및 이메일 초대 플로우
이메일 검증 훅과 태그 UI를 분리해 팀 생성 → 초대코드 발급 → 메일 발송까지 한 모달에서 처리하는 플로우를 구현했습니다.
Problem
팀 생성 시 여러 이메일을 입력·검증·삭제해야 하고, 유효하지 않거나 중복된 이메일을 즉시 피드백해야 했습니다. 팀 생성 성공 후 초대코드 생성·이메일 발송·캐시 갱신까지 연속 처리가 필요했습니다.
Solution
isValidEmail 유틸과 EmailValidationResult 타입으로 검증 로직을 분리하고, useEmailList 커스텀 훅으로 emails·errorMessage·emailStatus 상태와 추가/삭제 핸들러를 캡슐화했습니다. CreateTeamModal에서 React Query mutation으로 팀 생성 후 generateInvitationCode → sendTeamInvitationEmail을 순차 실행하고, queryClient.invalidateQueries로 팀 목록을 갱신한 뒤 해당 팀 페이지로 navigate하도록 구성했습니다. Enter 키 입력·로딩 상태(disabled) 처리로 UX를 보완했습니다.
Task 템플릿 가져오기 모달
템플릿 검색·선택·편집·삭제를 하나의 모달에서 처리하고, React Query 캐시 무효화로 목록을 즉시 반영했습니다.
Problem
기존 Task 템플릿을 불러올 때 목록 조회·이름 검색·단일 선택·편집/삭제 확인이 필요했고, 삭제 후 캘린더 화면으로 복귀하는 흐름까지 연결해야 했습니다.
Solution
useApiQuery로 템플릿 목록을 조회하고 useMemo로 title 기준 실시간 필터링을 적용했습니다. TemplateCard 체크박스로 단일 선택 상태를 관리하고, 선택 없을 때 편집/삭제 버튼을 disabled 처리했습니다. 삭제·수정 시 범용 Modal로 확인 후 deleteTemplate API를 호출하고, 성공 시 invalidateQueries + navigate로 UI를 동기화했습니다. modalType prop으로 생성/편집 모드별 동작을 분기했습니다.
담당 업무
- · 캡스톤 FE — 디자인 시스템·공통 UI·팀/온보딩 도메인 담당
- · GitHub OAuth 로그인·Intro/Login 온보딩 플로우 구현
- React
- TypeScript
- TanStack Query
- Emotion
- React Router
HeartScore (MUVS)
2025.01 — 2025.08
피트니스 트레이너용 실시간 심박·운동 대시보드 웹앱. 외주 프로젝트로 임베디드 심박 센서·Spring 백엔드와 협업하며 FE를 단독 담당했습니다. 계약상 소스코드·GitHub 공개는 불가합니다. PWA로 iPad 등 태블릿 시연 환경에 최적화했습니다.
주요 성과
WebRTC 화면 공유 구현
getDisplayMedia + REST 시그널링으로 트레이너 대시보드를 외부 뷰어에 실시간 공유하고, 연결 끊김·재시작·수업 종료 시 PeerConnection을 안전하게 정리했습니다.
Problem
트레이너 iPad 대시보드를 원격(뷰모드) 기기에서도 봐야 했지만, 별도 스트리밍 서버 없이 브라우저만으로 화면을 전달해야 했습니다. Offer/Answer 교환 중 PeerConnection이 중복 생성되거나, 공유 중지·로그아웃·수업 종료 시 연결이 정리되지 않아 리소스가 누수될 위험이 있었습니다.
Solution
트레이너 측(DashboardPage)에서 getDisplayMedia로 화면 캡처 후 Offer·ICE Candidate를 서버에 PATCH하고, 뷰어(ShareDashboard)가 폴링으로 Answer를 수신해 연결을 완성하는 REST 기반 시그널링을 구축했습니다. resetPeerConnection()으로 기존 PC를 닫고 새 인스턴스를 생성해 다중 생성 버그를 해결했고, track.onended·connectionstatechange·로그아웃·수업 종료 각 시점에 스트림 정지 + disconnection API 호출로 라이프사이클을 통합 관리했습니다. 외부 네트워크 환경을 위해 Google STUN 서버도 추가했습니다.
시연 환경 IP 자동화
Wi-Fi·임베디드 IP를 자동 감지·연동해 현장 시연 세팅을 자동화하고, 연결 실패 시 fallback·재연결 흐름을 구현했습니다.
Problem
심박 센서·Spring이 로컬 Wi-Fi에 묶여 시연마다 IP를 수동으로 찾아 .env·소켓·QR 링크를 바꿔야 했습니다. 임베디드 모뎀 IP도 매번 직접 입력해야 WebSocket이 연결됐고, 잘못된 IP·모뎀 미가동 시 실패를 즉시 구분하기 어려웠습니다.
Solution
macOS/Windows npm 스크립트로 Wi-Fi IP를 읽어 FE·BE baseURL을 주입하고, SocketModal에 기기별 접속 URL을 안내했습니다. Firebase RTDB(ModemIP)에서 모뎀 IP를 자동 조회해 WebSocket을 연결하고, 2초 타임아웃 후 수동 입력 모달로 fallback했습니다. useWebSocket 훅에 5초 간격 자동 재연결을 두어 일시적 끊김도 복구했습니다.
실시간 심박 대시보드 및 기기 재연결 UX
WebSocket 심박 데이터를 회원 카드에 실시간 반영하고, 연결 끊김(Valid=3) 시 카드 내 ReconnectModal로 즉시 재페어링할 수 있게 했습니다. 1~25명 규모에 맞춰 카드·폰트·아이콘 크기를 단계별 반응형으로 설계했습니다.
Problem
수업 중 심박 밴드 연결이 끊기면 해당 회원 카드가 빈 상태로 남고, 대시보드를 나가지 않고 다른 기기에 재연결할 방법이 없었습니다. 참가 인원(1~25명)에 따라 카드 크기·정보 밀도가 달라져야 했고, iPad 9세대 등 시연 기기 해상도별 레이아웃도 맞춰야 했습니다.
Solution
WebSocketContext + ConnectedMembersContext로 MAC 기반 심박·Valid·배터리 데이터를 DashboardCard에 매핑했습니다. Valid===3일 때 ReconnectModal을 오버레이해 연결 가능 기기 목록을 API(/devices/connection)로 조회·재페어링할 수 있게 했습니다. count(참가 인원) prop에 따라 카드·닉네임·심박수·아이콘 크기를 4단계(1~4 / 5~9 / 10~16 / 17~25)로 스케일링했고, zonePercent 유지 로직으로 일시 연결 끊김 시 운동 강도 표시가 초기화되지 않도록 했습니다.
PWA·QR 회원가입·운동 기록 시각화
PWA fullscreen·서비스워커로 태블릿 앱처럼 시연하고, QR 코드로 회원가입 URL을 공유하며, recharts로 최근 수업 kcal·pts 추이를 그래프로 표시했습니다.
Problem
시연은 주로 iPad에서 진행되어 브라우저 주소창·상단바가 UX를 방해했고, 회원 추가는 트레이너가 직접 입력하는 방식이라 현장에서 번거로웠습니다. 회원은 자신의 운동 기록을 한눈에 볼 수단이 없었습니다.
Solution
manifest.json에 display: fullscreen·아이콘·service-worker를 적용해 PWA로 설치 가능하게 했습니다. 회원 추가 플로우를 QRCodeModal·QRCodePage로 전환해 Wi-Fi IP 기반 동적 URL(/signup) QR을 생성했고, 화면 비율에 맞춰 QR 크기를 자동 조정했습니다. UserMainPage에서 /users/lessons API로 최근 수업 ID를 자동 조회한 뒤 recharts LineChart로 kcal·pts 이중 Y축 그래프를 렌더링하고, 기록 없을 때 예외 UI를 분기했습니다.
담당 업무
- · 외주 프로젝트 FE 단독 담당
- · 임베디드·Spring과 연동한 실시간 심박 대시보드 전체 구현
- React
- JavaScript
- styled-components
- WebSocket
- WebRTC
- Axios
- PWA
- recharts
- React Router
물렐루야!
전국 연합 동아리 SW 해커톤 대상 - 물렐루야 (2024.09)
건강한 수분 섭취 습관 형성 서비스. 음료 종류별 개인 수분 섭취량 기록·관리와 루틴·통계 기능으로 지속 가능한 수분 섭취 습관을 돕는 웹 애플리케이션. 4인 팀(FE 2, BE 2)으로 참여해 구름톤 대상을 수상했습니다.
담당 업무
- · 프로젝트 초기 세팅 및 환경 구축 총괄
- · 서비스 메인 랜딩 페이지 및 주요 기능 인터랙션 UI 개발
- · 사용자 수분 섭취 데이터 시각화 및 루틴/기록 페이지 구현
- React
- SCSS
- styled-components
- Tailwind CSS
- axios
포트폴리오
개인 포트폴리오 사이트. Next.js App Router와 Tailwind CSS로 미니멀한 UI를 구현했습니다.
- Next.js
- TypeScript
- Tailwind CSS
수상
전국 연합 동아리 SW 해커톤 대상 - 물렐루야
2024.09
구름톤
1위, 단장상