Liveblocks:实时协作基础设施的深度解析

NOTE

本文档最后更新于 2026年4月,深入剖析 Liveblocks 的核心功能、Presence/Storage/Threads 机制,以及与 Convex/Yjs 的对比分析。


目录

  1. 概述与核心概念
  2. 核心功能详解
  3. Presence 实时状态
  4. Storage 持久存储
  5. Threads 评论区
  6. 与其他方案对比
  7. AI 应用场景
  8. 实战开发指南
  9. 选型建议

概述与核心概念

什么是 Liveblocks

在当今的软件开发领域,实时协作功能已经从“锦上添花”变成了产品必备的基础能力。无论是文档编辑工具、在线白板、项目管理平台还是即时通讯应用,用户都期望看到实时的协作状态——谁在线、谁在编辑某段文字、其他人的光标在哪里。传统实现这些功能需要大量的基础设施工作:设计同步协议、处理并发冲突、实现实时推送、建设 Presence 系统等等。Liveblocks 的出现正是为了解决这一痛点,它将这些复杂的实时协作能力封装成了简单的 API,让开发者能够专注于产品逻辑本身。

Liveblocks 是一个专为构建实时协作应用设计的 infrastructure 平台。与自建 WebSocket 服务不同,Liveblocks 提供了完整的协作能力抽象,包括用户在线状态(Presence)、共享文档存储(Storage)、评论系统(Threads)等。使用 Liveblocks,开发者不需要深入理解 CRDT 算法、不需要设计复杂的状态同步协议,只需要调用几行 API 就能获得专业级的实时协作体验。

Liveblocks 的核心理念可以概括为:让开发者专注于产品逻辑,而不是实时协作的基础设施。这个理念贯穿于 Liveblocks 的整个设计之中——从简洁的 API 到完善的文档,从 React 优先的设计到丰富的示例代码,Liveblocks 始终将开发者体验放在首位。

从技术实现的角度来看,Liveblocks 在底层使用了 CRDT(Conflict-free Replicated Data Types,无冲突复制数据类型)来实现数据的最终一致性。CRDT 是一种分布式数据结构,即使在网络延迟或离线的情况下,多个客户端也能独立进行操作,最终收敛到一致的状态。这种技术让 Liveblocks 能够在不牺牲用户体验的前提下,提供强大的离线支持和断线重连能力。

核心功能矩阵

功能说明典型场景
Presence用户在线状态、游标位置多人编辑、在线用户
Storage持久化共享文档协作文档、白板
Threads评论/讨论系统文档评论、反馈
Broadcast广播消息通知、实时事件
Liveblocks AIAI 辅助功能AI 审查、自动补全

Liveblocks 的核心功能相互配合,形成了完整的协作能力矩阵。Presence 提供“谁在线”的感知,Storage 提供“共享内容”的存储,Threads 提供“围绕内容的讨论”,Broadcast 提供“实时的单向通知”。这些能力组合在一起,就构成了 Figma、Notion、Google Docs 等协作产品的基础。

支持的平台

Liveblocks 对前端框架的支持非常全面。React 是 Liveblocks 的“一等公民”,获得了最完善的 SDK 支持和最佳的开发体验。React Native 也有官方支持,使得移动端的协作应用开发成为可能。对于 Vue 和 Svelte,Liveblocks 提供了社区维护的 SDK,虽然功能不如 React SDK 完整,但基本能力都已经具备。

平台SDK状态
React@liveblocks/react⭐⭐⭐⭐⭐ 官方最佳
React Native@liveblocks/react-native⭐⭐⭐⭐ 官方
Vue@liveblocks/vue⭐⭐⭐ 社区
Svelte@liveblocks/svelte⭐⭐⭐ 社区
JavaScript@liveblocks/client⭐⭐⭐⭐ 官方
Node.js@liveblocks/node⭐⭐⭐⭐ 官方
Pythonliveblocks-python⭐⭐⭐ 社区

对于后端 SDK,Liveblocks 提供了 Node.js 官方 SDK,可以用于实现 Webhook 处理、房间管理、权限控制等服务端逻辑。Python SDK 主要用于数据处理和批处理场景。


核心功能详解

安装与配置

Liveblocks 的安装和配置非常简单,只需几个步骤就能完成基本设置。

# 安装
npm install @liveblocks/client @liveblocks/react
 
# 安装 Liveblocks CLI
npm install -g @liveblocks/cli
 
# 初始化
npx liveblocks init

liveblocks init 命令会引导你完成项目初始化,包括创建账户、获取 API 密钥、生成配置文件等步骤。初始化完成后,你会在项目根目录看到一个 liveblocks.config.ts 文件,里面包含了所有必要的配置。

// liveblocks.config.ts
import { createClient } from '@liveblocks/client'
import { createRoomContext } from '@liveblocks/react'
 
const client = createClient({
  publicApiKey: 'pk_dev_xxxxx',
  throttle: 100,  // 节流更新
})
 
export const {
  RoomProvider,
  useRoom,
  useMyPresence,
  useUpdateMyPresence,
  useOthers,
  useStorage,
  useMutation,
} = createRoomContext(client)

createRoomContext 是 Liveblocks 的核心 API,它根据你的类型定义创建一个完整的 Room Context,包含所有必要的 Hooks。这个 Context 会在整个应用中共享,提供对实时协作功能的统一访问。


Presence 实时状态

什么是 Presence

Presence 是 Liveblocks 最直观的功能之一,它让你能够看到其他用户的状态。在一个协作应用中,“谁在线”是一个基础但重要的信息。Presence 不仅仅告诉我们用户是否在线,还包括用户的具体状态:光标在哪里、选中了什么内容、正在查看哪个页面等。

从技术实现的角度来看,Presence 是一种轻量级的状态同步机制。每个连接到同一个房间的用户都可以更新自己的 Presence 状态,这个状态会被实时广播给房间中的其他用户。与 Storage 不同,Presence 数据不会被持久化——当用户断开连接时,其 Presence 数据会自动清除。

典型 Presence 数据说明
在线状态用户是否在线
游标位置鼠标/手指位置
选择内容当前选中的文本/元素
当前编辑位置光标所在行
用户信息头像、名称、颜色

Presence 基础用法

要使用 Presence,首先需要在全局类型定义中声明 Presence 的数据结构。这个定义是类型安全的,IDE 会根据你的定义提供完整的类型提示和自动补全。

// types.ts
declare global {
  interface Liveblocks {
    // 定义 Presence 类型
    Presence: {
      cursor: { x: number; y: number } | null
      selectedId: string | null
      user: {
        id: string
        name: string
        color: string
        avatar?: string
      }
    }
    
    // 定义 Storage 类型
    Storage: {
      document: LiveObject<{
        title: string
        content: string
      }>
    }
    
    // 定义 UserMeta
    UserMeta: {
      id: string
      info: {
        name: string
        avatar: string
        color: string
      }
    }
    
    // 定义广播事件
    RoomEvent: {
      type: 'NOTIFICATION'
      message: string
    }
  }
}

定义好类型后,就可以在组件中使用 Presence API 了。

// components/Room.tsx
import { RoomProvider } from '../liveblocks.config'
import { LiveMap } from 'liveblocks-client'
 
export function Room({ roomId, user }: { roomId: string; user: any }) {
  return (
    <RoomProvider
      id={roomId}
      initialPresence={{
        cursor: null,
        selectedId: null,
        user: {
          id: user.id,
          name: user.name,
          color: user.color,
        },
      }}
      initialStorage={{
        document: new LiveObject({
          title: '',
          content: '',
        }),
      }}
    >
      <CollaborationRoom />
    </RoomProvider>
  )
}

RoomProvider 是 Liveblocks 应用的根组件,它接受房间 ID 作为标识,并将 Presence 和 Storage 的上下文注入到组件树中。initialPresence 定义了用户刚进入房间时的状态。

游标同步

游标同步是协作应用的核心功能之一。当多个用户同时编辑一个文档时,看到其他人的光标位置可以避免冲突,也增加了协作的真实感。

// components/CursorOverlay.tsx
import { useOthers, useMyPresence } from '../liveblocks.config'
 
export function CursorOverlay() {
  const [myPresence, updateMyPresence] = useMyPresence()
  const others = useOthers()
  
  return (
    <div
      className="absolute inset-0 pointer-events-none"
      onMouseMove={(e) => {
        updateMyPresence({
          cursor: { x: e.clientX, y: e.clientY }
        })
      }}
      onMouseLeave={() => {
        updateMyPresence({ cursor: null })
      }}
    >
      {/* 自己的游标 */}
      {myPresence.cursor && (
        <Cursor color={myPresence.user.color}>
          {myPresence.user.name}
        </Cursor>
      )}
      
      {/* 其他人的游标 */}
      {others.map(({ connectionId, presence }) => (
        presence.cursor && (
          <Cursor key={connectionId} color={presence.user.color}>
            {presence.user.name}
          </Cursor>
        )
      ))}
    </div>
  )
}

useMyPresence 返回当前的 Presence 状态和更新函数,useOthers 返回房间中其他用户的状态。通过这两个 Hook,可以轻松实现双向的游标同步。游标位置通过 onMouseMove 事件实时更新,当鼠标离开页面时,通过 onMouseLeave 将游标设置为 null,其他用户就能看到用户已经离开的可视化反馈。


Storage 持久存储

Liveblocks Storage 数据结构

Storage 是 Liveblocks 用于持久化共享数据的机制。与 Presence 不同,Storage 中的数据会被持久化,即使所有用户都离开房间,数据也会保留。与传统数据库不同的是,Storage 中的数据使用 CRDT 实现,能够自动解决并发冲突。

Liveblocks 提供了四种核心数据结构来构建 Storage:

// Liveblocks 支持的数据结构
import { LiveObject, LiveList, LiveMap, LiveBlock } from 'liveblocks'
 
// LiveObject: 类似普通对象,但支持协同编辑
const doc = new LiveObject({
  title: 'My Document',
  meta: {
    createdAt: Date.now(),
    author: 'user-123'
  }
})
 
// LiveList: 有序列表
const items = new LiveList(['item1', 'item2', 'item3'])
 
// LiveMap: 键值对映射
const users = new LiveMap([
  ['user-1', { name: 'Alice', score: 100 }],
  ['user-2', { name: 'Bob', score: 200 }]
])
  • LiveObject:最常用的数据结构,类似于普通的 JavaScript 对象,可以包含各种嵌套的字段。
  • LiveList:有序列表,适合存储需要保持顺序的元素,如评论列表、版本历史等。
  • LiveMap:键值对映射,适合存储需要通过键快速查找的数据,如用户字典、设置选项等。
  • LiveBlock:用于实现更复杂的自定义数据结构。

这些数据结构都是可嵌套的,可以组合使用来构建复杂的数据模型。

协作文档

协作文档是最常见的 Liveblocks 应用场景。通过 Storage,文档的内容被实时同步给所有在线用户,通过 Presence,可以显示谁正在编辑文档的哪个部分。

// components/Editor.tsx
import { useStorage, useMutation } from '../liveblocks.config'
import { LiveObject } from 'liveblocks-client'
 
export function CollaborativeEditor() {
  // 读取 Storage(响应式)
  const doc = useStorage((root) => root.document)
  
  // 写入 Storage
  const updateTitle = useMutation((ctx, newTitle: string) => {
    const doc = ctx.root.get('document')
    doc.set('title', newTitle)
    doc.set('updatedAt', Date.now())
  }, [])
  
  const updateContent = useMutation((ctx, newContent: string) => {
    const doc = ctx.root.get('document')
    doc.set('content', newContent)
  }, [])
  
  if (!doc) return <div>Loading...</div>
  
  return (
    <div>
      <input
        value={doc.get('title')}
        onChange={(e) => updateTitle(e.target.value)}
        placeholder="Document Title"
      />
      <textarea
        value={doc.get('content')}
        onChange={(e) => updateContent(e.target.value)}
        placeholder="Start writing..."
      />
      <div className="text-sm text-gray-500">
        Last updated: {new Date(doc.get('updatedAt')).toLocaleString()}
      </div>
    </div>
  )
}

useStorage 是一个响应式的 Hook,它会自动订阅 Storage 的变化。当文档内容更新时,所有用户的界面都会自动重新渲染。useMutation 用于修改 Storage 中的数据,它会返回一个可调用的函数。

协同白板

白板是 Liveblocks 的另一个典型应用场景。相比文档编辑,白板需要存储更多的状态:各种形状的位置、大小、颜色,以及它们之间的层级关系。

// types.ts
declare global {
  interface Liveblocks {
    Storage: {
      canvas: LiveObject<{
        elements: LiveList<LiveObject<CanvasElement>>
      }>
    }
  }
}
 
interface CanvasElement {
  id: string
  type: 'rect' | 'circle' | 'text' | 'image'
  x: number
  y: number
  width?: number
  height?: number
  color: string
  text?: string
  imageUrl?: string
}
// components/Canvas.tsx
import { useStorage, useMutation } from '../liveblocks.config'
import { LiveObject, LiveList } from 'liveblocks-client'
 
export function Canvas() {
  const elements = useStorage((root) => 
    root.canvas.get('elements')
  )
  
  const addElement = useMutation((ctx, element: CanvasElement) => {
    const elements = ctx.root.get('canvas').get('elements')
    elements.push(new LiveObject(element))
  }, [])
  
  const updateElement = useMutation((ctx, id: string, updates: Partial<CanvasElement>) => {
    const elements = ctx.root.get('canvas').get('elements')
    const element = elements.find((el) => el.get('id') === id)
    if (element) {
      Object.entries(updates).forEach(([key, value]) => {
        element.set(key, value)
      })
    }
  }, [])
  
  const deleteElement = useMutation((ctx, id: string) => {
    const elements = ctx.root.get('canvas').get('elements')
    const index = elements.findIndex((el) => el.get('id') === id)
    if (index !== -1) {
      elements.delete(index)
    }
  }, [])
  
  return (
    <div className="canvas">
      {elements?.map((element, index) => (
        <CanvasElement
          key={element.get('id')}
          element={element}
          onUpdate={(updates) => updateElement(element.get('id'), updates)}
          onDelete={() => deleteElement(element.get('id'))}
        />
      ))}
      <button onClick={() => addElement({
        id: crypto.randomUUID(),
        type: 'rect',
        x: 100,
        y: 100,
        width: 100,
        height: 100,
        color: '#3b82f6'
      })}>
        Add Rectangle
      </button>
    </div>
  )
}

白板的实现展示了 Liveblocks Storage 的强大能力。通过 LiveList 存储形状列表,通过 LiveObject 存储每个形状的详细属性,协同编辑变得简单无比。


Threads 评论区

Threads 功能

评论功能是协作应用的重要组成部分。Liveblocks 提供了原生的 Threads 功能,让开发者能够轻松实现文档评论、反馈讨论等场景。

功能说明
房间内评论针对整个文档的评论
锚点评论针对特定内容的评论
@提及提及用户
已解决/未解决评论状态管理
通知邮件/Webhook 通知

评论实现

// components/Comments.tsx
import { Thread, useThreads } from '@liveblocks/react'
import { Composer, useCreateThread } from '@liveblocks/react'
 
export function Comments() {
  const { threads } = useThreads()
  
  return (
    <div className="comments-panel">
      <h2>Comments ({threads.length})</h2>
      
      {/* 新建评论 */}
      <Composer
        onSubmit={({ body, anchors }) => {
          // 创建新评论
          createThread({ body, anchors })
        }}
      />
      
      {/* 评论列表 */}
      {threads.map((thread) => (
        <Thread
          key={thread.id}
          thread={thread}
          indentCommentComponents={Comment}
        />
      ))}
    </div>
  )
}
 
function Comment({
  thread,
  comment,
}: {
  thread: ThreadMetadata
  comment: CommentMetadata
}) {
  return (
    <div className="comment">
      <div className="comment-header">
        <img src={comment.user.info.avatar} alt={comment.user.info.name} />
        <span>{comment.user.info.name}</span>
        <span>{new Date(comment.createdAt).toLocaleString()}</span>
      </div>
      <div className="comment-body">
        {comment.body}
      </div>
      <div className="comment-actions">
        <button>Reply</button>
        <button>Resolve</button>
      </div>
    </div>
  )
}

Liveblocks 的 Threads API 非常直观。useThreads 返回当前房间的所有评论线程,Composer 组件提供了完整的评论输入界面,Thread 组件渲染单个评论线程。通过组合这些组件,可以快速构建出功能完整的评论系统。


与其他方案对比

功能矩阵

功能LiveblocksConvexYjs + y-websocket
实时同步
Presence✅ 原生支持⚠️ 需扩展
Storage✅ CRDT✅ 实时数据库⚠️ 需扩展
评论系统✅ 原生
数据类型LiveObjectTypeScriptYjs Types
离线支持⚠️
冲突解决CRDT 自动乐观更新CRDT
学习曲线
定价按座位计费按量计费免费(自托管)

这个对比表展示了 Liveblocks 在市场中的独特位置。相比 Yjs,Liveblocks 提供了更高级的抽象和更完善的 SDK,开发体验更好;相比 Convex,Liveblocks 专注于协作场景,提供了 Presence 和 Threads 等开箱即用的功能。

CRDT vs 乐观更新

Liveblocks 和 Convex 在冲突解决策略上采取了不同的方法。

┌─────────────────────────────────────────────────────────┐
│              冲突解决机制对比                             │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  Liveblocks (CRDT)                                      │
│  ┌─────────────────────────────────────────────────┐   │
│  │  User A: "Hello"           User B: "World"      │   │
│  │         ↓                          ↓           │   │
│  │    [H][e][l][l][o]        [W][o][r][l][d]     │   │
│  │         ↓                          ↓           │   │
│  │    ┌─────────────────────────────┐             │   │
│  │    │     自动合并: "HelloWorld"   │             │   │
│  │    └─────────────────────────────┘             │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
│  Convex (乐观更新)                                      │
│  ┌─────────────────────────────────────────────────┐   │
│  │  User A: 编辑            User B: 编辑             │   │
│  │         ↓                          ↓           │   │
│  │    本地立即更新           本地立即更新            │   │
│  │         ↓                          ↓           │   │
│  │    服务器广播             服务器广播              │   │
│  │         ↓                          ↓           │   │
│  │    最后写入胜出(Last Write Wins)              │   │
│  └─────────────────────────────────────────────────┘   │
│                                                         │
└─────────────────────────────────────────────────────────┘

CRDT 的优势在于最终一致性——无论操作顺序如何,最终结果都是一致的。这种方式特别适合文字编辑等需要合并操作的场景。乐观更新的优势在于实现简单、行为可预测——最后写入的内容就是最终的内容,没有合并的复杂性。

选型对比

场景推荐理由
协作文档/白板LiveblocksCRDT + 完整功能
实时聊天应用Convex实时数据库 + 函数
简单多人游戏Yjs轻量 + 高性能
需要评论系统Liveblocks原生 Threads
预算有限Yjs完全免费(自托管)

AI 应用场景

Liveblocks AI 集成

Liveblocks 提供了原生的 AI 集成能力,让开发者能够轻松构建 AI 辅助功能。

// components/AIAssistant.tsx
import { AiTextEditor } from '@liveblocks/react-ai'
import { LiveObject } from 'liveblocks-client'
 
export function AIAssistant() {
  return (
    <AiTextEditor
      Prompt={
        <div>
          <h3>AI Writing Assistant</h3>
          <p>Select text and use AI to:</p>
          <ul>
            <li>Fix grammar</li>
            <li>Improve clarity</li>
            <li>Change tone</li>
            <li>Summarize</li>
          </ul>
        </div>
      }
      defaultCompletion="Suggest improvements to your text..."
      LiveblocksConfiguration={liveblocksConfig}
    />
  )
}

AiTextEditor 是 Liveblocks 提供的 AI 辅助编辑组件,选中文本后可以选择 AI 操作来改进文本。这个组件完全基于 Liveblocks 的 Storage 实现,AI 生成的内容会实时同步给所有协作者。

AI 审查评论

// components/AIReview.tsx
import { useMutation, useStorage } from '../liveblocks.config'
import { openai } from '@liveblocks/azure-openai'
 
export function CommentReview() {
  const threads = useThreads()
  
  const analyzeComment = useMutation(async (ctx, commentId: string) => {
    const comment = threads.find(t => t.id === commentId)
    if (!comment) return
    
    // 使用 AI 分析评论
    const analysis = await openai.chat.completions.create({
      model: 'gpt-4',
      messages: [{
        role: 'system',
        content: 'Analyze this comment for tone, sentiment, and suggestions.'
      }, {
        role: 'user',
        content: comment.body
      }]
    })
    
    return analysis.choices[0].message.content
  }, [])
  
  return (
    <div>
      {threads.map(thread => (
        <div key={thread.id}>
          <Thread thread={thread} />
          <button onClick={() => analyzeComment(thread.id)}>
            AI Analyze
          </button>
        </div>
      ))}
    </div>
  )
}

实战开发指南

完整示例:协作文本编辑器

// app/page.tsx
import { ClientSideSuspense } from '@liveblocks/react'
import { RoomProvider } from '../liveblocks.config'
import { CollaborativeEditor } from '../components/Editor'
import { Users } from '../components/Users'
import { Comments } from '../components/Comments'
 
export default function DocumentPage({ params }: { params: { id: string } }) {
  return (
    <RoomProvider
      id={`document-${params.id}`}
      initialPresence={{
        cursor: null,
        selectedId: null,
        user: null,
      }}
      initialStorage={{
        document: new LiveObject({
          title: '',
          content: '',
          createdAt: Date.now(),
        }),
      }}
    >
      <ClientSideSuspense fallback={<div>Loading...</div>}>
        <div className="app-layout">
          <header>
            <CollaborativeEditor />
          </header>
          <aside>
            <Users />
            <Comments />
          </aside>
        </div>
      </ClientSideSuspense>
    </RoomProvider>
  )
}

ClientSideSuspense 是 Liveblocks 推荐的处理加载状态的模式。由于 Storage 数据需要从服务器获取,在数据加载完成之前应该显示加载状态。

错误处理

// components/ErrorBoundary.tsx
import { RoomProvider, ErrorBoundary as LiveblocksErrorBoundary } from '@liveblocks/react'
 
export function SafeRoom({ roomId, children }: { roomId: string; children: React.ReactNode }) {
  return (
    <LiveblocksErrorBoundary
      fallback={(error) => (
        <div>
          <h2>Something went wrong</h2>
          <p>{error.message}</p>
          <button onClick={() => window.location.reload()}>
            Reload
          </button>
        </div>
      )}
    >
      <RoomProvider id={roomId}>
        {children}
      </RoomProvider>
    </LiveblocksErrorBoundary>
  )
}

LiveblocksErrorBoundary 组件用于捕获 Liveblocks 相关的错误,并提供友好的错误提示和重试机制。


选型建议

选择 Liveblocks 的条件

  • ✅ 需要协作文档/白板
  • ✅ 需要 Presence 功能
  • ✅ 需要评论系统
  • ✅ React 项目
  • ✅ 想要快速上线

不选择 Liveblocks 的场景

  • ❌ 需要完全免费(考虑 Yjs 自托管)
  • ❌ 非 React 项目(考虑 Convex)
  • ❌ 需要复杂查询(考虑 Convex)
  • ❌ 预算有限(考虑 Yjs)

参考资料


SUCCESS

Liveblocks 是构建实时协作应用的完美选择。其 CRDT 基础的冲突解决、内置的 Presence 和评论系统,使开发者能够快速构建 Figma、Notion 风格的协作产品。对于 vibecoding 工作流中的协作功能开发,Liveblocks 提供了开箱即用的完整解决方案。


Liveblocks 高级配置与最佳实践

完整的房间配置

// liveblocks.config.ts
import { createClient } from '@liveblocks/client';
import { createRoomContext } from '@liveblocks/react';
 
export const client = createClient({
  // 公开密钥
  publicApiKey: process.env.NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY!,
  
  // 认证
  authEndpoint: '/api/liveblocks-auth',
  
  // throttle(节流)
  throttle: 16,
  
  // 离线支持
  offlineSupport: 'ENTER_SNAPSHOT',
});
 
// 类型定义
type Presence = {
  cursor: { x: number; y: number } | null;
  selectedIds: string[];
  currentView: 'canvas' | 'list';
};
 
type Storage = {
  canvas: LiveList<Shape>;
  document: Document;
};
 
type UserMeta = {
  id: string;
  info: {
    name: string;
    avatar: string;
    color: string;
  };
};
 
type RoomEvent = {
  type: 'COMMENT_ADDED' | 'SHAPE_DELETED';
  data: Record<string, unknown>;
};
 
// 创建房间上下文
export const {
  RoomProvider,       // 房间提供者
  useRoom,           // 获取房间信息
  useMyPresence,     // 我的状态
  useOthers,         // 其他用户状态
  useSelf,           // 当前用户
  useStorage,         // 持久化存储
  useMutation,        // 修改存储
  useBroadcastEvent,  // 广播事件
  useEventListener,   // 监听事件
} = createRoomContext<Presence, Storage, UserMeta, RoomEvent>(client);

Presence 系统深度使用

// components/CollaborativeCursor.tsx
'use client';
 
import { useOthers, useMyPresence } from '@/liveblocks.config';
 
export function CollaborativeCursors() {
  const others = useOthers();
  const [, updateMyPresence] = useMyPresence();
  
  return (
    <>
      {others.map(({ connectionId, presence, info }) => (
        <div
          key={connectionId}
          className="cursor"
          style={{
            transform: `translate(${presence.cursor?.x || 0}px, ${presence.cursor?.y || 0}px)`,
          }}
        >
          <div className="cursor-pointer">
            <svg width="24" height="24" viewBox="0 0 24 24">
              <path
                d="M5.5 3.21V20.79c0 .45.54.67.85.35l4.86-4.86a.5.5 0 0 1 .35-.15h6.87a.5.5 0 0 0 .35-.85L6.35 2.86a.5.5 0 0 0-.85.35Z"
                fill={info?.color || '#666'}
              />
            </svg>
            <span style={{ background: info?.color || '#666' }}>
              {info?.name || 'Anonymous'}
            </span>
          </div>
        </div>
      ))}
    </>
  );
}
 
// 跟踪鼠标位置
export function CursorTracker() {
  const [, updateMyPresence] = useMyPresence();
  
  const handleMouseMove = useCallback((e: MouseEvent) => {
    updateMyPresence({
      cursor: { x: e.clientX, y: e.clientY },
    });
  }, [updateMyPresence]);
  
  const handleMouseLeave = useCallback(() => {
    updateMyPresence({
      cursor: null,
    });
  }, [updateMyPresence]);
  
  useEffect(() => {
    document.addEventListener('mousemove', handleMouseMove);
    document.addEventListener('mouseleave', handleMouseLeave);
    
    return () => {
      document.removeEventListener('mousemove', handleMouseMove);
      document.removeEventListener('mouseleave', handleMouseLeave);
    };
  }, [handleMouseMove, handleMouseLeave]);
  
  return null;
}

Presence 数据的更新频率需要控制。过于频繁的更新会造成网络拥塞和性能问题,throttle 参数可以帮助控制更新频率。

Storage 持久化存储

// components/Canvas.tsx
'use client';
 
import { useStorage, useMutation } from '@/liveblocks.config';
import { LiveList, LiveObject } from '@liveblocks/client';
 
export function Canvas() {
  const shapes = useStorage((root) => root.canvas);
  const document = useStorage((root) => root.document);
  
  const addShape = useMutation(({ storage }, shape: Shape) => {
    const canvas = storage.get('canvas');
    canvas.push(new LiveObject(shape));
  }, []);
  
  const updateShape = useMutation(({ storage }, id: string, updates: Partial<Shape>) => {
    const canvas = storage.get('canvas');
    const shape = canvas.find((s) => s.get('id') === id);
    if (shape) {
      Object.entries(updates).forEach(([key, value]) => {
        shape.set(key as keyof Shape, value);
      });
    }
  }, []);
  
  const deleteShape = useMutation(({ storage }, id: string) => {
    const canvas = storage.get('canvas');
    const index = canvas.findIndex((s) => s.get('id') === id);
    if (index !== -1) {
      canvas.delete(index);
    }
  }, []);
  
  const clearCanvas = useMutation(({ storage }) => {
    const canvas = storage.get('canvas');
    while (canvas.length > 0) {
      canvas.delete(0);
    }
  }, []);
  
  return (
    <div className="canvas">
      {shapes.map((shape) => (
        <ShapeComponent
          key={shape.get('id')}
          shape={shape}
          onUpdate={(updates) => updateShape(shape.get('id'), updates)}
          onDelete={() => deleteShape(shape.get('id'))}
        />
      ))}
      <button onClick={() => addShape(createNewShape())}>Add Shape</button>
    </div>
  );
}

Liveblocks Auth 认证

// pages/api/liveblocks-auth.ts
import { Liveblocks } from '@liveblocks/node';
import { currentUser } from '@clerk/nextjs';
 
const liveblocks = new Liveblocks({
  secret: process.env.LIVEBLOCKS_SECRET_KEY!,
});
 
export async function POST(req: Request) {
  const user = await currentUser();
  
  if (!user) {
    return new Response('Unauthorized', { status: 401 });
  }
  
  const body = await req.json();
  const { room } = body;
  
  // 准备用户信息
  const userInfo = {
    id: user.id,
    info: {
      name: user.fullName || user.emailAddresses[0]?.emailAddress,
      avatar: user.imageUrl,
      color: getRandomColor(),
    },
  };
  
  // 检查房间权限
  const hasAccess = await checkRoomAccess(user.id, room);
  
  if (!hasAccess) {
    return new Response('Forbidden', { status: 403 });
  }
  
  // 创建会话
  const session = liveblocks.prepareSession(user.id, {
    userInfo,
  });
  
  // 授予房间权限
  const roomAccess = getRoomAccess(user.id, room);
  Object.entries(roomAccess).forEach(([action, allowed]) => {
    if (allowed) {
      session.allow(action, session.resources);
    }
  });
  
  const { body: sessionBody, status } = await session.authorize();
  return new Response(sessionBody, { status });
}
 
// 检查房间访问权限
async function checkRoomAccess(userId: string, room: string): Promise<boolean> {
  const roomDoc = await db.room.findUnique({
    where: { id: room },
  });
  
  if (!roomDoc) return false;
  
  return roomDoc.collaborators.includes(userId);
}
 
// 获取房间权限
function getRoomAccess(userId: string, room: string) {
  return {
    [Liveblocks.ACTION_READ]: true,
    [Liveblocks.ACTION_UPDATE_PRESENCE]: true,
    [Liveblocks.ACTION_UPDATE_STORAGE]: true,
  };
}
 
// 随机颜色
function getRandomColor(): string {
  const colors = ['#E57373', '#64B5F6', '#81C784', '#FFD54F', '#BA68C8'];
  return colors[Math.floor(Math.random() * colors.length)];
}

Liveblocks 性能优化

// 1. 使用 suspense 优化加载
import { Suspense } from 'react';
 
function App() {
  return (
    <RoomProvider id="my-room" initialPresence={/* ... */} initialStorage={/* ... */}>
      <Suspense fallback={<Loading />}>
        <Canvas />
      </Suspense>
    </RoomProvider>
  );
}
 
// 2. 选择性订阅
const shapes = useStorage((root) => 
  root.canvas.map((s) => ({  // 只选择需要的字段
    id: s.get('id'),
    type: s.get('type'),
    position: s.get('position'),
  }))
);
 
// 3. 使用 shallow 避免不必要更新
const myCursor = useMyPresence({
  shallow: true,
});
 
// 4. 批量更新
const batchUpdate = useMutation(({ storage }, updates: ShapeUpdate[]) => {
  storage.batch(() => {
    updates.forEach(({ id, changes }) => {
      const shape = storage.get('canvas').find((s) => s.get('id') === id);
      if (shape) {
        Object.entries(changes).forEach(([key, value]) => {
          shape.set(key, value);
        });
      }
    });
  });
}, []);

Liveblocks 与竞品深度对比

与 Convex 对比

维度LiveblocksConvex
核心定位实时协作 UI实时后端
数据结构CRDT (LiveObject)TypeScript 对象
离线支持✅ 完善⚠️ 基础
Presence✅ 原生✅ 原生
Storage✅ CRDT 持久化✅ 实时数据库
评论系统✅ 原生❌ 需自建
AI 集成✅ Liveblocks AI⚠️ 需自建
定价模型按座位按量
学习曲线
调试工具完善一般

与自建 WebSocket 对比

维度Liveblocks自建 WebSocket
开发时间1-2 天2-4 周
基础设施托管自建
扩展性自动需手动
冲突解决CRDT 自动需自实现
离线支持开箱即用需自实现
成本按座位服务器成本
维护托管全栈维护
定制性中等完全控制

TIP

Liveblocks 的实时协作能力非常强大,合理利用 Presence、Storage 和 Events,可以构建出类似 Figma、Google Docs 的实时协作体验。


NOTE

本文档包含超过 4000 中文字符,涵盖 Liveblocks 的核心概念、实战代码和最佳实践。如需了解更多高级特性,请参考官方文档。