A collaborative document editor with real-time editing, comments, and organization support. Create documents from templates, edit with a rich WYSIWYG editor, and collaborate with presence and threaded comments.
Live deployment: https://docs-seven-peach.vercel.app
| Layer | Technology | Role |
|---|---|---|
| Framework | Next.js 15 (App Router) | SSR, RSC, file-based routing, API routes |
| Backend | Convex | Document persistence, pagination, full-text search, server-side auth |
| Auth | Clerk | Sign-in, user/org identity, Convex token integration |
| Real-time | Liveblocks | Collaborative editing, presence, comments, room-based sync |
| Editor | TipTap + Liveblocks React TipTap | Rich text (headings, lists, tables, images, links), CRDT-based collaboration |
| State | Zustand | Global editor instance for toolbar/menus; nuqs for URL search params |
| UI | Radix UI + Tailwind CSS | Accessible primitives and utility-first styling |
| Feedback | Sonner | Toasts for success and error messages |
Why these choices
- Next.js 15: Server components for document preloading and auth checks; single codebase for app and API.
- Convex: Type-safe queries/mutations, real-time subscriptions, and search indexes without managing a separate database or WebSocket layer.
- Clerk: Handles auth flows and org membership; integrates with Convex and Liveblocks via server-side token/session checks.
- Liveblocks: Off-the-shelf real-time collaboration (presence, comments, document sync) with a TipTap extension, reducing custom CRDT/OT work.
- TipTap: Extensible ProseMirror-based editor with a large extension ecosystem (tables, tasks, images, etc.) and first-class Liveblocks support.
High-level flow
- Entry: Middleware runs Clerk on all non-static routes. Root layout wraps the app in
NuqsAdapter,ConvexClientProvider(Clerk + Convex), and toaster. - Home (
/): Client fetches paginated documents from Convex (with optional search). User picks a template or opens an existing document; creating a doc runs a Convex mutation and redirects to/documents/[documentId]. - Document (
/documents/[documentId]): Server gets Clerk Convex token, preloads the document with ConvexpreloadQuery, then renders the clientDocumentcomponent. Only authenticated users with a valid token reach this page. - Document session:
Documentis wrapped in aRoom(Liveblocks). Room auth is handled byPOST /api/liveblocks-auth: it validates the user with Clerk, loads the document from Convex, enforces owner or organization membership, then returns a Liveblocks session scoped to that room. - Editor: TipTap is wired with
useLiveblocksExtension(and optional offline support). Toolbar and menus read/write the editor via a Zustand store. Comments and presence use Liveblocks React UI (e.g.Threads,Avatars). Document metadata (title, rename, delete) is persisted in Convex via mutations.
Data and access
- Convex:
documentstable (title, initialContent, ownerId, organizationId). Indexes by owner and organization; search index on title. Mutations: create, updateById, removeById. Queries: getById, getByIds, GetDocument (paginated, optionally filtered by search). - Liveblocks: One room per document (room id = document id). Storage holds editor state and layout (e.g. margins). Access is granted only to document owner or members of the document’s organization (resolved in the auth API using Convex + Clerk).
Auth chain
- Clerk → session and org membership.
- Convex → JWT from Clerk (
getToken({ template: "convex" })), used for Convex RPC and preload. - Liveblocks → Custom auth endpoint that checks Clerk session, fetches document from Convex, and allows access for owner or org members, then returns a Liveblocks-authorized body.
Prerequisites
- Node.js 18+
- Accounts and projects: Clerk, Convex, Liveblocks
Steps
-
Clone and install:
git clone <repository-url> cd Docs npm install
-
Environment variables (e.g.
.env.local):NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY– Clerk publishable keyCLERK_SECRET_KEY– Clerk secret (server)NEXT_PUBLIC_CONVEX_URL– Convex deployment URL (e.g. fromnpx convex dashboard)LIVEBLOCKS_SECRET_KEY– Liveblocks secret (used in/api/liveblocks-auth)
Configure Clerk to issue Convex JWTs (e.g. Convex JWT template in Clerk dashboard) and use the same Convex deployment URL in the app.
-
Convex dev (separate terminal):
npx convex dev
Pushes schema and functions; keep it running while developing.
-
Next.js dev:
npm run dev
Open http://localhost:3000. Sign in with Clerk; create a document from the home page or a template, then open it to use the editor and collaboration.
-
Production build:
npm run build npm run start
src/
app/
(home)/ # Home route group
page.tsx # Dashboard: templates + document list
navbar.tsx # Logo, search, org switcher, user
search-input.tsx # Search tied to URL (nuqs)
templates-gallery.tsx # Template carousel, create doc via Convex
document-table..tsx # Paginated table, load more
document-row.tsx # Row + menu (open, rename, delete)
document-menu.tsx
documents/[documentId]/
page.tsx # RSC: auth + preload, render Document
document.tsx # Layout: Room, Navbar, Toolbar, Editor
room.tsx # Liveblocks provider, auth endpoint, resolveUsers
editor.tsx # TipTap + Liveblocks extension, ruler
toolbar.tsx # Formatting, font, color, links, images, etc.
navbar.tsx # Doc title, File/Edit/Insert/Format, avatars
threads.tsx # Liveblocks anchored/floating threads
avatars.tsx # Presence avatars
inbox.tsx # Comments inbox
document-input.tsx # Editable title, rename mutation
ruler.tsx # Margin ruler using Liveblocks storage
actions.ts # Server actions: getUsers, getDocuments (Convex)
api/
liveblocks-auth/
route.ts # POST: Clerk + Convex doc check, Liveblocks authorize
layout.tsx # Root layout, fonts, Convex + Clerk + nuqs
globals.css
error.tsx
components/
convex-client-provider.tsx # ClerkProvider, ConvexProviderWithClerk, auth gates
fullscreen-loader.tsx
remove-dialog.tsx
rename-dialog.tsx
ui/ # Radix-based UI primitives
hooks/
use-debounce.ts
use-mobile.tsx
use-search-param.ts # nuqs wrapper for search
use-toast.ts
store/
use-editor-store.ts # Zustand: current TipTap editor instance
lib/
utils.ts
constants/
templates.ts # Template definitions (title, initialContent, image)
extensions/
font-size.ts # TipTap custom extension
line-height.ts
middleware.ts # Clerk middleware matcher
convex/
schema.ts # documents table, indexes, search index
documents.ts # Queries and mutations for documents
auth.config.ts # Convex auth provider (Clerk)
The app is deployed on Vercel. Set the same environment variables in the Vercel project, ensure Convex is deployed and the Convex URL points to the production deployment, and connect the repo for automatic builds and previews.