- ↗ 5개 레포 MSA 아키텍처 솔로 풀스택 설계 및 구현
- ↗ 랜딩 페이지 배포 완료 (castcanvaslab.com)
- ↗ 캔버스 코어·Inspector 패널·디자인 시스템 구현 완료
왜 만드는가
리서치를 할 때마다 같은 문제를 겪었다. 좋은 아티클은 노션에, 레퍼런스 이미지는 피그마에, 메모는 애플 노트에 흩어진다. 꺼내 볼 때마다 탭을 10개씩 열어야 하고, 아이디어 사이의 연결은 내 머릿속에만 존재한다.
노션은 선형적이고, 피그마는 문서를 다루기 불편하다. PDF, 이미지, 노트를 하나의 캔버스 위에 올려두고 연결하는 것 — 이게 내가 원하는 도구였다.
Notion보다 더 공간적으로, Figma보다 더 문서 친화적으로.
아키텍처
[ cast-canvas-lab-site ] castcanvaslab.com (랜딩)
|
▼ 앱 진입
[ cast-canvas-lab-fe ] app.castcanvaslab.com (워크스페이스)
React 19 + @xyflow/react + Yjs client
| \
| REST API \ WebSocket
▼ ▼
[ cast-canvas-lab-be ] [ cast-canvas-lab-collab ]
Spring Boot 3.5 NestJS 11 + Yjs
- 인증/인가 (JWT) - room lifecycle
- 워크스페이스 API - state-vector 동기화
- 캔버스 메타데이터 - update 브로드캐스트
- 노드/엣지 CRUD - awareness 전파
- S3 signed URL 발급 - BE 권한 검증 연동
- 검색 API
|
┌────────┼────────┐
▼ ▼ ▼
PostgreSQL Redis S3
레포지토리 구성
| 레포 | 역할 | 상태 |
|---|---|---|
| orchestrator | API 계약, 크로스 레포 태스크 단일 소스 | 운영 중 |
| fe | 워크스페이스 캔버스 앱 | 개발 중 |
| be | REST API 서버 | 개발 중 |
| collab | 실시간 협업 서버 | 구조 완료 |
| site | 퍼블릭 랜딩 사이트 | 배포 완료 |
기술 선택 이유
@xyflow/react (React Flow v12) 노드와 엣지로 요소를 연결하는 캔버스를 처음부터 구현하는 건 너무 복잡하다. React Flow는 드래그, 연결, 줌, 뷰포트를 모두 제공하면서 커스텀 노드를 React 컴포넌트로 자유롭게 만들 수 있다. DocumentNode(PDF), ImageNode, NoteNode를 독립 컴포넌트로 구성할 수 있어 선택했다.
Yjs + NestJS (collab 서버) — 직접 구현 prebuilt 협업 백엔드(e.g., Hocuspocus)를 쓰지 않고 직접 구현했다. room lifecycle, state-vector 기반 초기 동기화, update 브로드캐스트, awareness 전파를 직접 소유해야 세부 제어가 가능하다. y-protocols 표준 인코딩을 기반으로 프로토콜 명세를 먼저 작성하고, COLLAB과 FE 양쪽이 그것을 따라 구현하는 방식을 택했다.
Spring Boot (BE) + Flyway 캔버스 메타데이터, 워크스페이스, 사용자 데이터는 관계형 구조가 맞다. Flyway로 스키마 변경을 코드로 추적하고, 로컬-프로덕션 간 DB 상태 불일치를 막는다.
오케스트레이터 레포 패턴
5개 레포가 각자 작업하면 FE↔BE API 계약, FE↔COLLAB WebSocket 프로토콜이 불일치할 위험이 있다. orchestrator 레포에 contracts/openapi.yaml, contracts/collab-protocol.md, tasks/MASTER_TASKS.md를 단일 소스로 두고, 각 레포 에이전트는 이 계약을 기준으로 구현한다.
사용자 요청
↓
1. MASTER_TASKS.md → 현재 상태 파악
2. ARCHITECTURE.md → 영향 레포 식별
3. contracts/ → 계약 변경 필요 여부 판단
↓ 계약 변경 필요
contracts/ 먼저 수정 → 각 레포에 위임
↓ 단일 레포 변경
해당 레포에 직접 위임
↓
MASTER_TASKS.md 완료 상태 업데이트
Yjs 협업 프로토콜
collab 서버와 FE 클라이언트 간 WebSocket 프로토콜을 직접 설계했다. 모든 메시지는 바이너리(Uint8Array)로 전송하고 y-protocols 인코딩을 따른다.
초기 동기화 흐름:
클라이언트 서버
|── WebSocket 연결 ──────────>| (토큰 검증, BE 권한 확인)
|<── sync(step1: serverSV) ──| 서버 state vector 전송
|── sync(step2: missing) ───>| 누락 업데이트 전송
|── sync(step1: clientSV) ──>| 클라이언트 state vector
|<── sync(step2: missing) ──| [동기화 완료]
awareness 상태 스키마:
interface AwarenessState {
user: { id: string; nickname: string; color: string };
cursor: { x: number; y: number } | null; // 캔버스 좌표
}
비로그인 사용자는 COLLAB에 연결할 수 없으며 로컬 Yjs만 사용한다.
현재까지 구현된 것
랜딩 사이트 (완료) Next.js + AWS S3 + CloudFront + Route53. main 브랜치 push 시 GitHub Actions로 자동 배포. → castcanvaslab.com
캔버스 코어 (완료)
- 무한 캔버스 pan/zoom
- DocumentNode (PDF), ImageNode, NoteNode 컴포넌트
- 커스텀 엣지 (삭제 버튼 포함)
- 노드 이동·리사이즈·삭제·다중 선택
- 파일 드롭 → 노드 생성
- 빈 캔버스 안내 UI
Inspector 패널 (완료)
- DocumentInspector: react-pdf 기반 PDF 뷰어
- ImageInspector
- NoteInspector: 텍스트 편집 (blur-on-save)
디자인 시스템 (완료) SCSS Modules 기반 디자인 토큰, 타이포그래피, 라이트/다크 테마
BE 기본 인프라 (완료) JWT 회원가입·로그인 API, 사용자 프로필 API, Flyway 마이그레이션 구조
COLLAB 서버 구조 (완료) NestJS WebSocket 서버, Room registry, use-case 레이어(join/auth/update/awareness/disconnect)
지금 만들고 있는 것
MASTER_TASKS 기준으로 병렬 진행 중:
- BE: 워크스페이스 CRUD API, Redis 연동, S3 signed URL 발급
- FE: 인증 UI (로그인/회원가입), 워크스페이스 목록·생성 페이지
- 이후 통합: 인증 연동 → 워크스페이스 연동 → 캔버스 저장/불러오기 → 파일 업로드 → 협업 연결