빠른 시작
두 브라우저에서 공동 편집을 실행한 뒤 최소 Client, Transport, Endpoint, Service, Adapter 구성을 애플리케이션으로 옮깁니다.
이 페이지는 두 경로로 진행합니다. 먼저 공식 예제를 실행하여 두 브라우저에서 전체 공동 편집 흐름을 검증합니다. 그다음 최소 Server와 Web 구성을 읽고 자신의 Univer 애플리케이션에 적용합니다.
시작 전에 환경 및 버전 요구 사항을 충족하세요. 모든 @univerjs/*와 @univerjs-pro/* 패키지는 같은 호환 릴리스 집합으로 고정해야 합니다.
경로 1: 공식 예제 실행
실행 전 준비
저장소는 Node.js 24+, pnpm 10.32.1을 요구합니다. node --version, pnpm --version을 확인하고 잠금 파일을 유지하세요. 모든 pnpm example:* 명령은 저장소 루트에서 실행합니다. 이 명령은 프런트엔드와 서버 빌드 및 타입 검사 후 서버를 시작하며 핫 리로드 서버가 아닙니다. 소스를 수정하면 중지 후 다시 실행하세요.
클라이언트 라이선스가 있다면 빌드 전에 셸에서 UNIVER_LICENSE를 설정하세요. vite.config.ts가 브라우저 번들에 삽입합니다. 이미 빌드된 서버를 시작할 때만 설정하면 반영되지 않습니다. 라이선스 설정을 참고하세요.
SQLite 예제는 설치 시 better-sqlite3를 빌드하므로 node-gyp, Python, C/C++ 도구가 필요합니다. node-gyp: command not found가 나오면 개발 환경에 node-gyp를 설치하고 예제 실행에 사용할 동일한 Node.js 버전으로 의존성을 다시 설치하세요.
git clone https://github.com/dream-num/univer-collaboration-examples.gitcd univer-collaboration-examplespnpm install --frozen-lockfilepnpm example:quick-start다음을 여세요.
http://127.0.0.1:3010/?unit=quick-start-sheet&type=2실시간 공동 편집 확인
- 한 브라우저 창에서 전체 URL을 여세요.
- 다른 브라우저 프로필이나 시크릿 창에서도 여세요.
- 어느 쪽 Sheet에서든 셀을 변경하세요.
- 다른 브라우저에 같은 변경이 나타나는지 확인하세요.
서버가 두 온라인 Session을 생성하도록 독립된 브라우저 컨텍스트를 사용하세요. 같은 페이지에서 변경을 보는 것만으로 Room, ACK, 브로드캐스트 경로를 검증할 수는 없습니다.
성공으로 확인되는 내용
Univer Collaboration Client → Node Transport → UniverCollabEndpoint → UniverCollabService → MemoryDatabaseAdapter두 브라우저의 동기화는 HTTP로 Unit을 로드하고 일회용 Session Ticket으로 WebSocket Session을 연 다음 같은 Room에 참여했음을 의미합니다. Service는 OT를 수행하고 새 리비전을 확정해 Adapter로 저장하며 Endpoint는 이를 다른 구성원에게 전파합니다.
경로 2: 애플리케이션에 통합
다음 코드는 Server와 Web의 최소 구성을 보여 줍니다. 전체 애플리케이션 구조는 공식 빠른 시작을 참고하세요.
기존 Univer Web 애플리케이션이 있으면 Runtime, 프리셋, UI를 유지하고 아래 Collaboration Client 설정과 Server만 추가하세요. 빈 프로젝트라면 빠른 시작 디렉터리의 index.html, vite.config.ts, 스타일을 애플리케이션 틀로 복사하세요.
실행 가능한 파일을 기준으로 이전
examples/quick-start를 복사하고 package.json, index.html, vite.config.ts, web/styles.css, web/main.ts, server/main.ts를 유지하세요. Express는 작업 디렉터리 기준으로 dist/web을 읽으므로 예제 디렉터리에서 build와 start를 실행합니다. Vite 라이선스 정의와 #app 높이도 유지하세요. 아래 코드는 조립 과정을 설명하며 완전한 프로젝트는 이 파일들에서 시작합니다.
예제 페이지와 /universer-api는 같은 출처를 사용합니다. 별도 프런트엔드를 사용하면 HTTP와 WebSocket Upgrade를 프록시하거나 클라이언트의 다섯 URL을 함께 변경하세요. unit은 백엔드가 만든 ID와 같아야 하고 type=2는 Sheets입니다. URL 매개변수만으로 문서를 만들지는 않습니다.
1. 호환되는 동일 버전 패키지 집합 설치
서버에는 Transport, Endpoint, Service, Database Adapter 하나가 필요하고 브라우저에는 Collaboration Client가 필요합니다. 필요한 패키지를 설치한 뒤 모든 Univer 패키지를 정확히 같은 버전으로 고정하세요.
pnpm add \ @univerjs-pro/collaboration@1.0.0 \ @univerjs-pro/collaboration-client@1.0.0 \ @univerjs-pro/collaboration-client-ui@1.0.0 \ @univerjs-pro/collaboration-database-memory@1.0.0 \ @univerjs-pro/collaboration-endpoint@1.0.0 \ @univerjs-pro/collaboration-service@1.0.0 \ @univerjs-pro/collaboration-transport-node@1.0.0 \ @univerjs-pro/license@1.0.0 \ @univerjs/core@1.0.0 \ @univerjs/preset-sheets-core@1.0.0 \ @univerjs/presets@1.0.0 \ @univerjs/protocol@1.0.0 \ express react react-dom rxjs현재 패키지 구성과 정확한 버전은 실행 가능한 공식 예제의 매니페스트를 기준으로 합니다.
2. Service, Endpoint, 초기 Unit 생성
UniverCollabService는 확정 상태를 관리하는 공동 편집 핵심입니다. Endpoint는 브라우저 프로토콜을 Service에 연결하고 Memory Adapter는 스냅샷, 변경 집합, 리비전을 임시 저장합니다.
import { createServer } from "node:http";import express from "express";import type { IWorkbookData } from "@univerjs/core";import { LocaleType } from "@univerjs/core";import { MemoryDatabaseAdapter } from "@univerjs-pro/collaboration-database-memory";import { UniverCollabEndpoint } from "@univerjs-pro/collaboration-endpoint";import { UniverCollabService } from "@univerjs-pro/collaboration-service";import { createNodeTransport } from "@univerjs-pro/collaboration-transport-node";import { ErrorCode, UniverType } from "@univerjs/protocol";const UNIT_ID = "quick-start-sheet";const unitData: IWorkbookData = { id: UNIT_ID, rev: 1, name: "Quick Start Sheet", appVersion: "", locale: LocaleType.EN_US, sheetOrder: ["sheet-1"], sheets: { "sheet-1": { id: "sheet-1", name: "Sheet 1", rowCount: 100, columnCount: 26, cellData: {}, }, }, styles: {}, resources: [],};const database = new MemoryDatabaseAdapter();const service = new UniverCollabService({ dbAdapter: database });const endpoint = new UniverCollabEndpoint(service);const transport = createNodeTransport();transport.use(async (context, next) => { context.userID = "demo-user"; await next();});transport.register(endpoint);await service.createUnitFromData( { type: UniverType.UNIVER_SHEET, data: unitData }, { userID: "demo-user" },);고정 demo-user는 신뢰할 수 있는 신원이 Transport에서 Endpoint와 Service로 전달되는 예입니다. 운영 애플리케이션은 transport.use() 안에서 쿠키, Bearer 토큰 또는 세션을 검증한 후 안정적인 업무용 userID를 설정해야 합니다.
3. 권한 부여, HTTP, WebSocket 진입점 연결
현재 Collaboration Client는 권한 부여 프로토콜을 조회합니다. 빠른 시작은 해당 경로에서 항상 allowed: true를 반환하고 Transport를 애플리케이션의 HTTP 서버에 연결합니다.
const app = express();app.post("/universer-api/authz/-/object/-/batch_allowed", express.json(), (request, response) => { const body = request.body as { requests: Array<{ unitID: string; objectID: string; actions: unknown[] }>; }; response.json({ error: { code: ErrorCode.OK, message: "" }, objectActions: body.requests.map((item) => ({ unitID: item.unitID, objectID: item.objectID, actions: item.actions.map((action) => ({ action, allowed: true })), })), });});app.use(express.static("dist/web"));const server = createServer(app);transport.attach(server);server.listen(3010, "127.0.0.1");항상 허용하는 설정은 학습용 단순화이며 보안 경계가 아닙니다. 실제 애플리케이션은 HTTP 읽기, 실시간 JOIN, 변경 집합 제출, Unit 수명 주기 작업을 보호해야 합니다. 전체 범위는 신원 및 권한 부여를 참고하세요.
4. 브라우저 Runtime에 Collaboration Client 등록
일반 Web SDK와 프리셋을 유지한 채 공동 편집 플러그인과 프로토콜 URL을 추가하세요.
import { LocaleType, LogLevel } from "@univerjs/core";import { UniverCollaborationPlugin } from "@univerjs-pro/collaboration";import { UniverCollaborationClientPlugin } from "@univerjs-pro/collaboration-client";import CollaborationClientEnUS from "@univerjs-pro/collaboration-client/locale/en-US";import { BrowserCollaborationSocketService, UniverCollaborationClientUIPlugin,} from "@univerjs-pro/collaboration-client-ui";import CollaborationClientUIEnUS from "@univerjs-pro/collaboration-client-ui/locale/en-US";import { UniverLicensePlugin } from "@univerjs-pro/license";import { UniverSheetsCorePreset } from "@univerjs/preset-sheets-core";import UniverPresetSheetsCoreEnUS from "@univerjs/preset-sheets-core/locales/en-US";import { createUniver, defaultTheme, mergeLocales } from "@univerjs/presets";import "@univerjs/preset-sheets-core/lib/index.css";import "@univerjs-pro/collaboration-client-ui/lib/index.css";const httpProtocol = location.protocol === "https:" ? "https" : "http";const wsProtocol = location.protocol === "https:" ? "wss" : "ws";const baseURL = `${httpProtocol}://${location.host}/universer-api`;createUniver({ locale: LocaleType.EN_US, locales: { [LocaleType.EN_US]: mergeLocales( UniverPresetSheetsCoreEnUS, CollaborationClientEnUS, CollaborationClientUIEnUS, ), }, theme: defaultTheme, logLevel: LogLevel.WARN, collaboration: true, presets: [UniverSheetsCorePreset({ container: "app" })], plugins: [ [UniverLicensePlugin, { license: import.meta.env.UNIVER_LICENSE || undefined }], UniverCollaborationPlugin, [ UniverCollaborationClientPlugin, { socketService: BrowserCollaborationSocketService, sendChangesetTimeout: 200, authzUrl: `${baseURL}/authz`, snapshotServerUrl: `${baseURL}/snapshot`, collabSubmitChangesetUrl: `${baseURL}/comb`, collabWebSocketUrl: `${wsProtocol}://${location.host}/universer-api/comb/connect`, wsSessionTicketUrl: `${baseURL}/user/session-ticket`, }, ], UniverCollaborationClientUIPlugin, ],});페이지에는 id="app" 요소가 필요하며 쿼리 매개변수로 Unit을 선택합니다.
/?unit=quick-start-sheet&type=25. 학습용 설정을 애플리케이션 기능으로 교체
| 학습용 설정 | 교체 대상 |
|---|---|
고정 demo-user | 애플리케이션 인증 Middleware 및 안정적인 사용자 ID |
| 항상 허용하는 권한 검사 | 읽기, JOIN, 제출, 수명 주기를 포괄하는 서버 ACL |
| 고정 Unit | createUnitFromData()를 호출하는 생성 API 및 제품 레코드 |
| Memory Adapter | SQLite 또는 계약 테스트를 통과한 사용자 지정 영구 저장 Adapter |
다음으로 데이터베이스 어댑터와 신원 및 권한 부여를 완성하세요. 필요에 따라 History, Thread Comment, Worktree, Exchange를 추가하세요. 모든 실행 가능한 구성은 예제 목록을 참고하세요.
첫 실행 문제 해결
| 증상 | 확인 |
|---|---|
| 설치·빌드 실패 | Node.js 24+, 지정 pnpm, 동일 Univer 버전 |
| 3010 사용 중 | 이전 예제 중지 또는 PORT=3011 설정 후 두 브라우저 URL 변경 |
| 빈 화면·리소스 누락 | 빌드 완료, 예제 디렉터리에서 시작, #app 높이 |
| 편집 동기화 실패 | 같은 전체 URL, snapshot과 session-ticket 성공, /universer-api/comb/connect WebSocket 연결 |
| 재시작 후 데이터 소실 | Quick Start는 메모리 저장이므로 SQLite 예제로 진행 |
이 가이드는 어떠셨나요?