"Figma 이미지와 스크린샷 크기가 안 맞아서 비교가 안 돼요." 문제의 시작pixelDiff는 디자인(Figma)과 실제 구현(스크린샷)을 픽셀 단위로 비교하는 도구다. 그런데 개발 초기부터 골치 아픈 문제가 있었다.Figma에서 내보낸 이미지: 5760×3600px크롬 익스텐션으로 캡처한 스크린샷: 2880×1800px사용자가 직접 업로드한 이미지: 1440×900px세 이미지 모두 "1440×900 프레임"을 캡처한 건데, 실제 픽셀 크기는 전부 다르다. 왜 이런 일이 발생하는가Figma API의 고해상도 내보내기Figma API로 이미지를 요청할 때 scale 파라미터를 지정할 수 있다. scale=4로 요청하면 원래 크기의 4배 해상도로 내보내진다.// Figma API 이미지 요청const ..
Turborepo가 해결하려는 문제Turborepo 공식 문서를 보면 이런 말이 나온다. "Turborepo is a high-performance build system for JavaScript and TypeScript codebases." 핵심 기능은 세 가지다. 기능 설명 로컬/리모트 캐싱 변경 없는 패키지는 빌드 스킵 병렬 실행 의존성 없는 태스크 동시 실행 증분 빌드 변경된 패키지만 다시 빌드 대규모 모노레포에서 빌드 시간이 대폭 단축되는 사례가 많다. 팀원 간 리모트 캐시를 공유하면 CI 비용도 절감된다. 근데 나는 그 규모가 아니다pixelDif..
상태 관리 라이브러리를 고를 때 Redux, Recoil, Jotai, Zustand 사이에서 고민하는 경우가 많다. 하나의 전역 스토어에 모든 상태를 넣을지, 아토믹하게 쪼갤지, 아니면 그 중간 어딘가를 선택할지는 프로젝트 규모와 팀 상황에 따라 다르다. pixelDiff를 개발하면서 Zustand를 선택하고, "도메인 기반 복합 스토어" 패턴을 적용했다. 단순한 투두 앱이 아니라 캔버스 에디터 수준의 복잡도를 가진 프로젝트에서 상태 관리를 어떻게 설계했는지 정리한다. 왜 Zustand인가상태 관리 라이브러리 선택지를 비교해보면 이렇다.라이브러리특징적합한 상황Redux단일 스토어, 보일러플레이트 많음대규모 팀, 엄격한 구조 필요Recoil아토믹, React 종속Facebook 생태계, Suspense ..
웹앱에서 iframe 내부의 정보를 가져오려면 어떻게 해야 할까? 일반적으로 postMessage를 쓰면 되지만, cross-origin iframe이라면 Content Security Policy에 막힌다. pixelDiff는 디자인 시안과 실제 웹사이트를 비교하는 서비스라서, 사용자가 입력한 URL을 iframe으로 띄워야 한다. 당연히 cross-origin이고, iframe 내부의 높이, URL 변화, 스크린샷까지 가져와야 했다.선택지: 직접 통신 vs 중계자방식동작한계postMessage 직접iframe ↔ parentcross-origin이면 수신 불가Chrome Extensioncontent script가 양쪽에 주입구조가 복잡해짐서버 중계WebSocket으로 연결실시간성 부족, 인프라 부담..
여러 디바이스 프레임에서 동시에 웹사이트를 미리보기 하는 기능을 만들었다. iPhone, iPad, Galaxy 등 다양한 해상도의 iframe이 나란히 배치되고, 사용자가 하나를 스크롤하면 나머지도 따라 움직여야 한다.문제는 iframe이 cross-origin이라는 것이다. 보안상 다른 origin의 iframe 내부에 직접 접근할 수 없다. DOM을 읽을 수도, 스크롤 위치를 설정할 수도 없다. 선택지방식설명한계iframe.contentWindow.scrollTo()직접 스크롤 제어cross-origin 차단SharedWorker탭 간 통신iframe에서 사용 불가BroadcastChannel탭 간 통신same-origin만 가능postMessage윈도우 간 메시지 전달origin 검증 필요cros..
canvas 위에서 레이어를 비교할 때 opacity 조절이 핵심이다. 문제는 이 UI가 원래 좌측 하단에 고정되어 있었다는 것.canvas 작업은 상호작용이 많다. 레이어를 드래그하고, 확대/축소하고, 위치를 맞추는 동안 시선은 canvas 중앙에 머문다. 그런데 opacity 값을 확인하려면 시선을 좌측 하단으로 옮겨야 했다. 작업 흐름이 끊긴다.해결책은 opacity 숫자를 슬라이더 thumb 바로 위에 표시하는 것이었다. 드래그하는 손가락 근처에서 값을 바로 확인할 수 있다. 화면도 덜 가린다.그런데 단순히 숫자만 띄우면 밋밋했다. 드래그하는 방향으로 살짝 기울어졌다가 손을 떼면 다시 원위치로 돌아오는 애니메이션을 추가했다. 오뚜기처럼. 실용적인 이유도 있지만, 솔직히 만들면서 재밌었다. 슬라이더..
런타임에 터진 버그"타입 정의 완벽하게 했는데 왜 undefined 에러가...?"type User = { name: string; age: number;};// 컴파일은 통과하지만...const data: User = JSON.parse(response);// response가 { name: 123, age: "스물" }이면? API 응답이 예상과 달랐다. TypeScript는 아무 경고 없이 통과시켰다.TypeScript는 컴파일 타임에만 동작한다. API 응답, 폼 입력, 외부 데이터는 런타임에 들어오기 때문에 타입을 아무리 정교하게 정의해도 런타임에는 무력하다.결국 if (!data.name || typeof data.age !== 'number') 같은 검증 코드를 직접 작성하게 되는데, 이..
DB를 어디에 둘까사이드 프로젝트를 시작할 때 가장 먼저 부딪히는 질문이 있다. DB를 어디에 둘 것인가.선택지는 크게 두 가지다. 서버에 직접 PostgreSQL을 설치하거나, 관리형 서비스를 쓰거나.직접 설치하면 비용은 아끼지만 백업, 모니터링, 버전 업그레이드를 전부 직접 해야 한다. 사이드 프로젝트는 본업 끝나고 틈틈이 하는 건데, DB 관리까지 신경 쓰고 싶지 않았다.관리형 서비스로 방향을 잡았다. 선택지 비교PostgreSQL을 지원하는 관리형 서비스 중 무료 티어가 있는 것들을 추렸다. 서비스DB 엔진무료 티어Prisma 호환특징SupabasePostgreSQL500MB, 무제한✅Auth, Storage 등 부가 기능PlanetScaleMySQLHobby 플랜 종료✅Vitess 기반, 브랜칭..