Skip to content

프론트엔드 아키텍처

apps/web React SPA의 구조와 핵심 패턴입니다.

기술 선택

영역선택이유
빌드Vite 6빠른 HMR, ESM 네이티브
라우팅TanStack Router타입 안전 파일 기반 라우팅
스타일Tailwind CSS 4유틸리티 퍼스트, @tailwindcss/vite
캔버스Fabric.js 7객체 기반 2D 캔버스, Textbox 등
상태Zustand경량, slice 합성, persist/devtools

라우트 구조

src/routes/
├── __root.tsx              # 루트 + AuthRedirectHandler
├── index.tsx               # / (홈 — 로그인·워크스페이스 진입)
├── login/
│   └── route.tsx           # /login (Google OAuth, redirect 쿼리 지원)
└── canvas/
    ├── route.tsx           # /canvas 레이아웃 (에디터에서만 SidebarProvider)
    ├── index.tsx           # /canvas/ — 워크스페이스 목록·생성
    └── $workspaceId.tsx    # /canvas/:id — 캔버스 에디터

TanStack Router Vite 플러그인이 routeTree.gen.ts를 자동 생성합니다.

API 연동

경로API 호출
/loginGET {VITE_API_URL}/auth/google (OAuth 리다이렉트)
/GET /users/me, POST /auth/logout
/canvas/GET /workspaces, POST /workspaces
/canvas/:idGET /workspaces/:id, GET/PUT /workspaces/:id/nodes

모든 API 요청은 credentials: 'include'로 JWT 쿠키를 전송합니다.

인증·접근 제어

  • /canvas, /canvas/:id 진입 시 beforeLoad에서 GET /users/me로 인증 확인 → 미인증 시 /login?redirect=...
  • /canvas/:idGET /workspaces/:id로 멤버십 확인 → 403/404 시 /canvas?error=...로 리다이렉트
  • OAuth 복귀 후 AuthRedirectHandlersessionStorage의 redirect URL로 복귀

Canvas 컴포넌트

components/canvas/index.tsx는 Fabric Canvas의 React 래퍼입니다:

tsx
export function Canvas({ className }: CanvasProps) {
  const containerRef = useRef<HTMLDivElement>(null);
  const domRef = useRef<HTMLCanvasElement>(null);
  const [canvas, setCanvas] = useState<fabric.Canvas | null>(null);

  useFabricCanvas(domRef, { onCanvas: setCanvas, containerRef });
  useGlobalShortcuts();
  useCanvasCamera(canvas);
  useCanvasViewportWheel({ canvasContainerRef: containerRef, canvas });
  useCanvasHydration(canvas);
  useDrawingTools(canvas);
  useCanvasSelection(canvas);
  useCanvasNodes(canvas);

  return (
    <div ref={containerRef} className={`relative touch-none overscroll-none ${className ?? 'h-full w-full'}`}>
      <canvas ref={domRef} />
      <NodeLabelsOverlay canvas={canvas} />
    </div>
  );
}

각 hook은 단일 책임을 가집니다:

Hook책임
useFabricCanvasCanvas 인스턴스 생성/해제, ResizeObserver로 canvasSize 동기화
useGlobalShortcuts전역 단축키 → COMMANDS 실행
useCanvasCamerazoom, pan 상태 → viewportTransform
useCanvasViewportWheel휠 이벤트 → 줌/팬
useCanvasHydration서버 로드 완료 후 store → Fabric 객체 복원
useDrawingTools도구별 노드 배치 (placement)
useCanvasSelectionFabric selection ↔ selectedIds, 삭제 요청 처리
useCanvasNodesnodes 변경 ↔ Fabric sync (이동·리사이즈·편집·삭제)

/canvas/:id 페이지는 추가로 useWorkspaceDocument(workspaceId)를 호출해 노드를 서버에서 로드하고 변경 시 debounce(500ms) 저장합니다.

Features 모듈

features/
├── auth/
│   ├── api.ts                  # fetchMe, ApiError
│   └── AuthRedirectHandler.tsx # OAuth 후 redirect 복귀
├── workspace/
│   ├── api.ts                  # workspaces / nodes API 클라이언트
│   ├── components/
│   │   └── WorkspaceHub.tsx    # 목록·생성 UI
│   └── hooks/
│       └── useWorkspaceDocument.ts  # 로드 + debounce 저장
├── canvas/
│   ├── hooks/
│   ├── utils/
│   ├── labels/
│   └── drawing/
└── shortcuts/
    └── hooks/

UI 컴포넌트

  • components/ui/ — shadcn/ui (Button, Sidebar, Input, Badge, Sheet, DropdownMenu 등)
  • components/canvas/zoomLevel.tsx, position.tsx — 좌상단 줌·팬 좌표 HUD
  • components/propertiesSidebar/ — 우측 속성 패널 (현재 placeholder 메뉴만)
  • components/themeProvider.tsx — 다크/라이트 테마

/canvas/:id 페이지는 ModeToggle, ZoomLevel, Position, Canvas, "워크스페이스" 복귀 버튼을 조합합니다.

전역 단축키

Canvas 컴포넌트에서 useGlobalShortcuts()를 호출합니다. 이 hook이 COMMANDS 배열을 순회하며 keydown 이벤트를 처리하고, keyCombo.tsmod+z 등 플랫폼별 키 매핑을 처리합니다.

개발 도구

  • TanStack Router Devtools — 라우트 디버깅
  • Zustand devtools — Redux DevTools 연동
  • window.devCanvas — DEV 모드 콘솔 디버깅 API (global-injection.ts)
    • store.app — Zustand store
    • canvas — 현재 Fabric Canvas
    • getSelectedNodeIds(), getNodeById(id), getNodes(), getNodeOrder()

관련 문서

Canvas — 시각적 프로토타입 에디터