Collaboration

Real-time presence and multiplayer features through Liveblocks.

@repo/collaboration provides real-time collaborative features through Liveblocks. apps/app uses it for an avatar stack and live cursors.

Collaboration is enabled by the existence of the LIVEBLOCKS_SECRET environment variable.

How it works

Liveblocks organises collaboration into rooms. Both halves of the setup — user authentication and the providers — are already wired in this package.

Two provider props are wired with it: resolveUsers and resolveMentionSuggestions, which resolve users and mention suggestions against the Clerk directory.

Usage

Liveblocks provides a number of hooks making it easy to add real-time presence and document storage to your app. For example, useOthers returns which users are currently connected, helpful for building avatars stacks and multiplayer cursors.

toolbar-avatars.tsx
import { useOthers, useSelf } from "@liveblocks/react/suspense";

export function ToolbarAvatars() {
  const others = useOthers();
  const me = useSelf();

  return (
    <div>
      {/* Your avatar */}
      <Avatar src={me.info.avatar} name={me.info.name} />

      {/* Everyone else's avatars */}
      {others.map(({ connectionId, info }) => (
        <Avatar key={connectionId} src={info.avatar} name={info.name} />
      )}
    </div>
  );
}

Multiplayer documents

You can take your collaborative app a step further, and set up multiplayer document state with useStorage and useMutation. This is ideal for creating custom experiences, such as a a multiplayer drawing panel, spreadsheet, or just a simple shared input and that anyone can edit.

collaborative-input.tsx
import { useStorage, useMutation } from "@liveblocks/react/suspense";

function CollaborativeInput() {
  // Get the input's value
  const inputValue = useStorage((root) => root.inputValue);
  
  // Set the input's value
  const setValue = useMutation(({ storage }, newValue) => {
    storage.set("inputValue", newValue);
  }, []);
  
  return <input value={inputValue} onChange={(e) => setValue(e.target.value)} />;
}

Commenting

Liveblocks also provides ready-made customizable components for adding collaboration, such as Thread and Composer.

comments.tsx
import { useThreads } from "@liveblocks/react/suspense";
import { Thread, Composer } from "@liveblocks/react-ui";

function Comments() {
  // Get each thread of comments and render them
  const { threads } = useThreads();

  return (
    <div>
      {threads.map((thread) => (
        <Thread key={thread.id} thread={thread} />
      ))}
      <Composer />
    </div>
  );
}

Collaborative editing

To add a rich-text editor with full collaborative editing and floating comments, you can set up a Tiptap or Lexical editor in a few lines of code.

collaborative-editor.tsx
import { useLiveblocksExtension, FloatingComposer, FloatingThreads } from "@liveblocks/react-tiptap";
import { useThreads, useEditor, EditorContent } from "@tiptap/react";
import StarterKit from "@tiptap/starter-kit";

export function Editor() {
  const { threads } = useThreads();
  const liveblocks = useLiveblocksExtension();

  // Set up your multiplayer text editor
  const editor = useEditor({
    extensions: [
      liveblocks,
      StarterKit.configure({ history: false }),
    ],
    immediatelyRender: false,
  });

  return (
    <div>
      <EditorContent editor={editor} />
      <FloatingThreads
        editor={editor}
        threads={threads}
      />
      <FloatingComposer editor={editor} />
    </div>
  );
}

Notifications

Liveblocks also provides notification components, meaning you can send in-app notifications to users that are tagged in comments, are mentioned in the text editor, or for any custom purpose.

notifications.tsx
import { useInboxNotifications } from "@liveblocks/react/suspense";
import {
  InboxNotification,
  InboxNotificationList,
} from "@liveblocks/react-ui";

export function CollaborativeApp() {
  // Get each notification for the current user
  const { inboxNotifications } = useInboxNotifications();

  return (
    <InboxNotificationList>
      {inboxNotifications.map((inboxNotification) => (
        <InboxNotification
          key={inboxNotification.id}
          inboxNotification={inboxNotification}
        />
      ))}
    </InboxNotificationList>
  );
}

Infrastructure

Liveblocks not only provides these features, but it also has:

Learn more by checking out the Liveblocks documentation, examples, and interactive tutorial.