Co-authored by
Hayden Bleaselnext-forge
Chris NicholasLiveblocks

next-forge maintains a collaboration package designed to provide real-time collaborative features to your apps. By default, we use Liveblocks as our collaboration engine. To showcase what you can do with this package, we’ve built a simple collaborative experience in the app application, featuring an avatar stack and live cursors.

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

How it works

Liveblocks relies on the concept of rooms, digital spaces where people collaborate. To set this up, you need to authenticate your users, and add the correct providers, however next-forge has already integrated this, meaning you can start building your collaborative application immediately.

We’ve also wired up two key props for the Liveblocks provider, resolveUsers and resolveMentionSuggestions, which are used to resolve the users and mention suggestions respectively.

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.