Onuri Studio — AI와 함께한 실시간 협업 화이트보드 개발 회고

1. 프로젝트 소개

Onuri Studio란?

"모두의 스토리, 우리의 스튜디오."

Onuri Studio는 URL 한 줄로 입장하는 실시간 협업 화이트보드 웹앱이다. FigJam과 유사한 무한 캔버스 위에서 여러 사용자가 동시에 도형을 그리고, 메모를 붙이고, 표를 만들 수 있다.

Channel
팀/프로젝트 공간
Story
화이트보드 단위
Collaborate
실시간 동시 편집

"방송 스튜디오" 메타포를 기반으로, 사용자는 채널(Channel) 에 입장해서 스토리(Story) 라는 화이트보드를 함께 만든다. 접속 중인 사용자에게는 "On Air" 빨간 인디케이터가 표시된다.

왜 만들었는가

  1. 실시간 협업 시스템을 처음부터 직접 구축해보고 싶었다 — CRDT vs LWW, presence, cursor sync, snapshot 보존, 재연결 복구 같은 문제를 코드로 부딪혀보기.
  2. tldraw v5의 custom shape/style/toolbar 실제 사용 한계를 탐색하고 싶었다.
  3. $0 예산 제약 안에서 실사용 가능한 서비스를 어디까지 만들 수 있는지 실험.
  4. Claude Code와의 페어 프로그래밍으로 어느 수준의 프로젝트까지 가능한지 검증.

개발 환경 & 기술 스택

영역선택비용
프레임워크Next.js 14 App Router + TypeScript무료
UITailwindCSS + shadcn/ui무료
캔버스tldraw v5 (Hobby License)무료
실시간 동기화Supabase Realtime broadcast + presence (LWW)무료 티어
DB + 인증Supabase Free (500MB DB, 50K MAU)무료
호스팅Vercel Hobby무료
AI 도구Claude Code (Anthropic, Opus 모델)유료
패키지 매니저pnpm무료

2. 개발 과정 — Claude Code와의 대화로 만든 21개의 결정

이 프로젝트의 특이점은 모든 설계와 구현이 Claude Code와의 대화를 통해 이루어졌다는 것이다. 초기 프로덕트 정의서(CLAUDE.md)를 프롬프트로 입력하고, 이후 Phase별로 코드를 생성하고, 발생하는 문제를 대화로 해결하며, 의사결정을 Decision Log로 누적해갔다.

Phase 구조와 진행

Phase 1완료
브랜딩 + 익명 인증 + 도메인 추상화
Phase 2완료
채널/스토리 CRUD + 마이페이지
Phase 3완료
단일 사용자 화이트보드 + 자동 저장
Phase 4완료
Realtime 동기화 + Presence + On Air
Phase 5완료
내보내기/가져오기 + 관리자 페이지
Phase 690%
보안 강화 + 모바일 + 스테이징 배포
Phase 730%
Google SSO
Phase 880%
Google Drive 연동
Phase 9대기
이메일 매직 링크 + 도메인 + 프로덕션

핵심 의사결정 (Decision Log)

개발 과정에서 21개의 의사결정(D-001 ~ D-021)이 Claude Code와의 대화를 통해 내려졌다. 그 중 프로젝트 방향에 가장 큰 영향을 준 결정들:

결정내용영향
D-010Yjs CRDT 대신 Supabase Realtime broadcast + LWW 채택학습 비용 절감, MVP 속도 확보. 50명 이상 시 마이그레이션 필요.
D-013Google SSO 조기 도입 (원래 Phase 7 계획)이메일 매직 링크 없이도 회원 가입 가능하게 만듦
D-016tldraw Editor abstraction L1lib/editor/index.ts 단일 모듈로 tldraw 표면 추상화 — 향후 에디터 교체 가능성 확보
D-017Realtime sync hardening + 25명 정원Smart autosave, Non-destructive reconnect, broadcast throttle 50ms batching
D-018Google Drive 연동 (Phase 8a + 8b)Picker SDK + drive.file scope + Shortcut + 폴더 자동 생성
D-019TableShape 커스텀 도형셀 편집/병합/스타일 — tldraw의 closed type union 우회 필요

3. 기술적 도전과 배운 것

3.1 Realtime 동기화 — CRDT vs Last-Write-Wins

프로젝트 초기에 가장 큰 결정은 동기화 전략이었다. Yjs CRDT는 이론적으로 완벽하지만 학습 곡선이 가파르고, tldraw와의 통합에 추가 작업이 필요했다.

Claude Code와의 대화에서 나온 결론:

"문제 크기에 맞는 도구를 선택하자. 25명 동시 편집이면 LWW + Smart autosave로 충분하다."

Yjs CRDT
  • 충돌 자동 해결
  • 오프라인 편집 후 병합
  • 학습 비용 높음
  • tldraw 통합 추가 작업 필요
50명+ 시 마이그레이션 예정
Supabase Realtime + LWW채택
  • 빠른 구현 (broadcast + presence)
  • Supabase 무료 티어로 충분
  • 25명 정원 cap으로 운영 가능
  • Smart autosave로 데이터 손실 방지
MVP에 적합

3.2 tldraw v5 — 라이브러리 내부를 읽어야 풀리는 문제들

tldraw는 강력한 캔버스 엔진이지만, v5에서 custom shape을 만들 때 문서화되지 않은 동작이 많았다.

겪은 문제들:

문제원인해결
Custom shape의 closed TLShape unionTypeScript가 새로운 shape 타입을 허용하지 않음@ts-expect-error로 우회
setEditingShape silent rejectcanEditShape 체크를 통과해야 동작 (Editor.mjs:1956)canEdit=true 조건 명시적 확인
옛 snapshot의 새 필드 누락 → store 손상schema 검증 실패 시 instance state 사라짐loadSnapshot 전 pre-migration JSON 변환
native dblclick suppressiontldraw가 더블클릭을 내부적으로 먹음onPointerDown에서 직접 카운팅
OverflowingToolbar 양쪽 렌더 quirkboundary item이 두 번 렌더됨createPortal(document.body) + position: fixed

3.3 익명 + 회원 병행 인증

Supabase의 anon 세션은 RLS에서 auth.uid()가 null이라 Postgres Changes를 수신할 수 없었다. 이 문제를 broadcast 채널 + admin client 서버 액션으로 우회했다.

익명 사용자의 알림 수신 경로: broadcast 채널 'user-notifications:{userId}' → Supabase Realtime (RLS 무관) → 클라이언트에서 직접 수신 초기 데이터 fetch: Server Action + admin client (Service Role) → RLS 우회 → 결과를 클라이언트에 전달

3.4 Google Drive API — 문서에 없는 함정들

Google Picker SDK에서 setAppId를 누락하면 404가 발생하는데, 이 에러 메시지는 전혀 도움이 되지 않았다. Claude Code와의 대화에서 GOOGLE_CLIENT_ID의 prefix 숫자가 Project Number라는 것을 알아내고 해결했다.

또한 drive.file scope는 sensitive scope라서 production에서 사용하려면 Google의 verification이 필요하다 — 도메인 인증, 데모 영상, 약관까지. 소규모 indie가 감당하기엔 큰 비용이어서, testing 모드 영구 운영 + 수동 등록 요청 workflow(D-021)로 전환했다.

3.5 $0 예산의 현실

서비스무료 티어 한도실 사용량위험도
Supabase500MB DB, 50K MAU충분낮음
VercelHobby (100GB bandwidth)충분낮음
Google OAuthtesting 모드 100명D-021로 관리중간
tldrawHobby Licenseproduction에서 5초 뒤 캔버스 사라짐높음

tldraw의 Hobby License 문제가 가장 큰 미해결 과제다. localhost에서는 정상 동작하지만, production 도메인에서는 라이선스 키 없이 5초 뒤 캔버스가 display:none 처리된다.

4. Claude Code와의 개발 워크플로우

어떻게 진행했는가

  1. 초기 프롬프트: 13개 섹션으로 구성된 프로덕트 정의서(CLAUDE.md)를 작성하여 제품의 전체 스펙을 입력
  2. Phase별 대화: 각 Phase 시작 시 목표를 명시하고, Claude Code가 코드를 작성 → 사용자가 검토하는 방식(D-004)
  3. 의사결정 누적: 대화 중 발생하는 모든 결정을 Decision Log로 기록 (CLAUDE.md 부록 A + DESIGN.md § 17 + README.md 세 곳 동시 갱신)
  4. HANDOFF.md 유지: 새 세션에서 작업을 이어갈 수 있도록 현재 상태를 기록

잘 된 점

  • 속도: 6주 분량의 MVP를 약 1주일 만에 Phase 5까지 완료
  • 일관성: 모든 코드가 동일한 아키텍처 패턴(Provider 추상화, usecase 레이어, zod 검증)을 따름
  • 문서화: Decision Log가 자동으로 누적되어, 왜 그렇게 결정했는지가 코드 옆에 남음
  • 탐색 비용 절감: tldraw, Supabase Realtime 같은 라이브러리의 문서화되지 않은 동작을 빠르게 파악

아쉬운 점

  • 검증 지연: AI가 생성한 코드를 바로 머지하면 나중에 side effect를 발견하는 경우가 있었음
  • 컨텍스트 한계: 세션이 길어지면 초기 결정 사항을 잊어버리는 경우 → HANDOFF.md로 보완
  • 라이선스/정책 이슈: tldraw Hobby License, Google OAuth verification 같은 비기술적 제약은 AI가 사전에 경고하기 어려움

5. 프로젝트 결론

현재 상태: Portfolio Mode (Archived)

상업화 unit economics를 계산한 결과:

  • FigJam, Miro, tldraw.com 등 기존 플레이어의 자본과 기능이 압도적
  • tldraw 상업 SDK License 비용
  • Supabase Pro 전환 비용
  • 예상 ARPU 대비 비현실적

결론적으로 포트폴리오 / 개인 사용 / 기술 학습 목적으로 아카이브하기로 결정했다.

보존 가치

이 프로젝트는 다음과 같은 실증적 기록을 남긴다:

Realtime Sync
Supabase Realtime + LWW로 25명 동시 편집을 $0으로 구현하는 패턴
tldraw Custom Shape
TableShape, gdrive-file, NoteAuthor 등 커스텀 도형 구현과 schema migration
Provider 추상화
인증/외부 통합을 어댑터 패턴으로 격리하여 Phase별 점진적 활성화
AI 페어 프로그래밍
Claude Code로 제품 정의 → 설계 → 구현 → 문서화까지 일관된 워크플로우

핵심 교훈

기능보다 단위 경제가 의사결정의 본질이다.

기술적으로 무엇이든 만들 수 있다는 자신감은 얻었지만, "만들 수 있다"와 "만들어야 한다"는 다른 질문이었다. $0 예산으로도 실사용 가능한 협업 도구를 만들 수 있지만, 그것을 사업으로 전환하려면 기술 외의 비용(라이선스, 인프라, 영업, 법적 요건)이 기술 비용을 압도한다.

라이브 데모: onuri-studio.vercel.app