A modern, real-time collaborative document editor with AI superpowers.
Built with Next.js 16, React 19, and Liveblocks.
Features • Demo • Tech Stack • Getting Started • Architecture • Contributing
- Live cursors — See where others are editing in real-time with colored name tags
- Presence awareness — Know who's currently viewing the document with avatars
- Instant sync — Changes appear immediately for all collaborators
- Conflict-free — Powered by Yjs CRDT for seamless merging
- Block-based editing — Notion-style blocks with BlockNote 0.46
- Slash commands — Quick formatting with
/menu - Markdown support — Write in markdown, see it rendered
- Dark/Light mode — Easy on the eyes, any time of day
- Document icons — Customize documents with emoji icons
Choose from 5 powerful AI models:
| Model | Provider | Best For |
|---|---|---|
| GPT-4o | OpenAI | General tasks, reasoning |
| Claude 3.5 Sonnet | Anthropic | Long documents, analysis |
| Gemini 1.5 Pro | Multi-modal, large context | |
| Mistral Large | Mistral | European data compliance |
| LLaMA 3.1 405B | Fireworks | Open-source, self-hostable |
AI Features:
- 🌐 Document Translation — Translate to 10 languages with streaming responses
- 💬 Chat with Document — Ask questions about your content
- ⚡ Streaming responses — See AI responses in real-time
- 📄 Smart truncation — Handles large documents intelligently (up to 400K chars)
- Role-based access — Owner and Editor roles with different permissions
- Easy sharing — Invite collaborators via email with validation
- User management — View and remove document access
- Ownership self-healing — Automatic recovery of corrupted ownership data
- Firebase Auth — Secure Google Sign-In authentication
- httpOnly cookies — Session tokens are not accessible via JavaScript
- Server-side validation — All sensitive operations verified on the server
- Firestore security rules — Granular access control at the database level
English, French, Spanish, German, Italian, Portuguese, Chinese, Russian, Hindi, Japanese
┌─────────────────────────────────────────────────────────────┐
│ 🟣 Spaces [Avatar] [Avatar] │
├─────────────────────────────────────────────────────────────┤
│ │
│ 📄 My Documents │ 📝 Document Title [Update] │
│ ├── 📋 Project Notes │ ───────────────────────────── │
│ ├── 📝 Meeting Summary │ │
│ └── 💡 Ideas │ Start typing here... │
│ │ │
│ 📤 Shared with me │ 👆 Cursor (You) │
│ └── 🗺️ Team Roadmap │ 👆 Cursor (Jane) │
│ │ │
│ [+ New Document] │ [Translate] [Chat] [🌙] │
│ │ │
└─────────────────────────────────────────────────────────────┘
| Technology | Version | Purpose |
|---|---|---|
| Next.js | 16.0.3 | React framework with App Router |
| React | 19.0.0 | UI library |
| TypeScript | 5.6.2 | Type safety |
| Technology | Version | Purpose |
|---|---|---|
| Liveblocks | 3.7.1 | Real-time infrastructure |
| Yjs | 13.6.19 | CRDT for conflict-free sync |
| BlockNote | 0.46.0 | Rich text editor |
| Technology | Version | Purpose |
|---|---|---|
| Vercel AI SDK | 6.0.3 | AI streaming & providers |
| Firebase | 12.7.0 | Auth & Firestore DB |
| Firebase Admin | 13.0.1 | Server auth & Firestore |
| Package | Version | Provider |
|---|---|---|
| @ai-sdk/openai | 3.0.1 | OpenAI |
| @ai-sdk/anthropic | 3.0.1 | Anthropic |
| @ai-sdk/google | 3.0.1 | |
| @ai-sdk/mistral | 3.0.1 | Mistral |
| Technology | Version | Purpose |
|---|---|---|
| Tailwind CSS | 4.0.9 | Utility-first CSS |
| Radix UI | Latest | Accessible primitives |
| Mantine | 8.3.8 | UI components |
| Framer Motion | 12.4.7 | Animations |
| Lucide | 0.563.0 | Icons |
| Sonner | 2.0.1 | Toast notifications |
| next-themes | 0.4.4 | Theme management |
| vaul | 1.0.0 | Drawer component |
- Node.js 20.9 or later (required by Next.js 16)
- npm 9.0 or later (or pnpm/yarn)
- A Liveblocks account (real-time)
- A Firebase project (auth & database)
- At least one AI provider API key
- Clone the repository
git clone https://github.com/yourusername/spacesapp.git
cd spacesapp- Install dependencies
npm install- Set up environment variables
cp .env.example .env.local-
Configure your environment (see Environment Variables)
-
Deploy Firestore security rules
Copy firestore.rules to Firebase Console → Firestore → Rules, or use Firebase CLI:
firebase deploy --only firestore:rules- Run the development server
npm run devCreate a .env.local file with the following variables:
# Liveblocks - https://liveblocks.io/docs/get-started
NEXT_PUBLIC_LIVEBLOCKS_PUBLIC_KEY=pk_...
LIVEBLOCKS_PRIVATE_KEY=sk_...# Client-side (from Firebase Console > Project Settings)
NEXT_PUBLIC_FIREBASE_APIKEY=...
NEXT_PUBLIC_FIREBASE_AUTHDOMAIN=...
NEXT_PUBLIC_FIREBASE_PROJECTID=...
NEXT_PUBLIC_FIREBASE_STORAGEBUCKET=...
NEXT_PUBLIC_FIREBASE_MESSAGINGSENDERID=...
NEXT_PUBLIC_FIREBASE_APPID=...
NEXT_PUBLIC_FIREBASE_MEASUREMENTID=... # Optional
# Server-side (from Firebase Console > Service Accounts > Generate Key)
FIREBASE_TYPE=service_account
FIREBASE_PROJECT_ID=...
FIREBASE_PRIVATE_KEY_ID=...
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n"
FIREBASE_CLIENT_EMAIL=...
FIREBASE_CLIENT_ID=...
FIREBASE_AUTH_URI=https://accounts.google.com/o/oauth2/auth
FIREBASE_TOKEN_URI=https://oauth2.googleapis.com/token
FIREBASE_AUTH_PROVIDER_X509_CERT_URL=https://www.googleapis.com/oauth2/v1/certs
FIREBASE_CLIENT_CERTS_URL=...
FIREBASE_UNIVERSE_DOMAIN=googleapis.com# OpenAI - https://platform.openai.com/api-keys
OPENAI_API_KEY=sk-...
# Anthropic - https://console.anthropic.com/
ANTHROPIC_API_KEY=sk-ant-...
# Google AI - https://makersuite.google.com/app/apikey
GOOGLE_GENERATIVE_AI_API_KEY=...
# Mistral - https://console.mistral.ai/
MISTRAL_API_KEY=...
# Fireworks (for LLaMA) - https://fireworks.ai/
FIREWORKS_API_KEY=...# Logs room ownership self-heal decisions on the server (dev only)
ROOM_OWNERSHIP_DEBUG=1src/
├── app/ # Next.js App Router
│ ├── api/ # API routes
│ │ ├── auth/session/ # Session cookie management (POST/DELETE)
│ │ ├── auth-endpoint/ # Liveblocks authentication
│ │ └── firebase-diagnostics/ # Firebase config debugging
│ ├── doc/[id]/ # Document pages
│ │ ├── page.tsx # Document view
│ │ ├── layout.tsx # Room provider + auth wrapper
│ │ ├── loading.tsx # Loading state
│ │ └── error.tsx # Error boundary
│ ├── layout.tsx # Root layout
│ └── page.tsx # Home page
│
├── components/ # React components
│ ├── ui/ # Shadcn UI primitives
│ │ ├── avatar.tsx # User avatar with fallback
│ │ ├── button.tsx # Button variants
│ │ ├── dialog.tsx # Modal dialogs
│ │ ├── drawer.tsx # Mobile drawer
│ │ ├── input.tsx # Form inputs
│ │ ├── popover.tsx # Popovers
│ │ ├── select.tsx # Dropdowns
│ │ ├── sheet.tsx # Sheet/drawer
│ │ ├── sonner.tsx # Toast notifications
│ │ ├── spinner.tsx # Loading spinner
│ │ └── tooltip.tsx # Tooltips
│ ├── AIDialog.tsx # Shared AI dialog base
│ ├── AIModelSelect.tsx # AI model dropdown
│ ├── Avatars.tsx # User presence avatars
│ ├── Breadcrumbs.tsx # Navigation breadcrumbs
│ ├── ChatToDocument.tsx # AI Q&A feature
│ ├── ClientOnly.tsx # SSR-safe wrapper
│ ├── DeleteDocument.tsx # Document deletion dialog
│ ├── Document.tsx # Main document component
│ ├── Editor.tsx # BlockNote editor wrapper
│ ├── EmojiPicker.tsx # Document icon picker
│ ├── ErrorBoundary.tsx # Error boundary components
│ ├── FirebaseAuthBridge.tsx # Auth state → session sync
│ ├── FollowPointer.tsx # Live cursor display
│ ├── Header.tsx # Top navigation bar
│ ├── InviteUser.tsx # User invitation dialog
│ ├── LiveBlocksProvider.tsx # Liveblocks context provider
│ ├── LiveCursorProvider.tsx # Cursor tracking provider
│ ├── ManageUsers.tsx # User management dialog
│ ├── NewDocumentButton.tsx # Create document button
│ ├── PageIcon.tsx # Document icon display
│ ├── RoomProvider.tsx # Document room wrapper
│ ├── Sidebar.tsx # Document navigation
│ ├── SidebarOption.tsx # Sidebar document item
│ ├── ThemeProvider.tsx # Dark mode provider
│ └── TranslateDocument.tsx # AI translation feature
│
├── hooks/ # Custom React hooks
│ ├── use-document-icon.ts # Document icon with optimistic updates
│ ├── use-document-title.ts # Document title management
│ ├── use-is-mounted.ts # Component mount tracking
│ ├── use-latest.ts # Latest value ref
│ ├── use-owner.ts # Ownership check
│ ├── use-room-id.ts # URL room ID extraction
│ ├── use-room-users.ts # Room users query
│ ├── use-streaming-request.ts # AI streaming handler
│ ├── use-user-documents.ts # User's documents query
│ └── index.ts # Central exports
│
├── lib/ # Utilities & server actions
│ ├── action-utils.ts # Server action response helpers
│ ├── document-utils.ts # Yjs document helpers
│ ├── documentActions.ts # Document CRUD (server actions)
│ ├── env.ts # Environment variable validation
│ ├── firebase-session.ts # Session cookie utilities
│ ├── firestore-helpers.ts # Firestore path helpers
│ ├── generateActions.ts # AI operations (server actions)
│ ├── liveblocks.ts # Liveblocks server client
│ ├── room-ownership.ts # Ownership self-healing
│ ├── stringToColor.ts # Color generation for users
│ └── utils.ts # General utilities
│
├── firebase/ # Firebase configuration
│ ├── firebaseConfig.ts # Client SDK setup
│ └── firebaseAdmin.ts # Admin SDK setup
│
├── constants/ # App constants
│ └── index.ts # AI models, languages, prompts, limits
│
└── types/ # TypeScript definitions
└── index.ts # Shared types
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Client │────▶│ Liveblocks │────▶│ Client │
│ (Editor) │◀────│ (Yjs) │◀────│ (Editor) │
└─────────────┘ └─────────────┘ └─────────────┘
│ │
▼ ▼
┌─────────────────────────────────────────────────────┐
│ Firebase │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │Documents │ │ Users │ │ Rooms (per user) │ │
│ └──────────┘ └──────────┘ └──────────────────┘ │
└─────────────────────────────────────────────────────┘
User ──▶ Firebase Auth (Google) ──▶ ID Token
│
▼
POST /api/auth/session
│
▼
httpOnly Session Cookie (5 days)
│
▼
POST /api/auth-endpoint (per room)
│
▼
Room Access Check (Firestore)
│
▼
Liveblocks Session Token
documents/
└── {documentId}/
├── title: string
├── icon: string | null # Emoji icon
├── createdAt: timestamp
└── updatedAt: timestamp
users/
└── {uid}/
└── rooms/
└── {documentId}/
├── roomId: string
├── userId: string
├── role: "owner" | "editor"
├── userEmail?: string # For lookup
└── createdAt: timestamp
All server actions use a standardized response format:
type ActionResponse<T> =
| { success: true; data: T }
| { success: false; error: { code: ActionErrorCode; message: string } };| Action | Purpose | Auth Required |
|---|---|---|
createNewDocument |
Create document + owner role | ✅ |
deleteDocument |
Delete document + all room entries | ✅ (owner) |
inviteUserToDocument |
Add editor by email | ✅ (owner) |
removeUserFromDocument |
Remove user access | ✅ (owner) |
updateDocumentIcon |
Update document emoji icon | ✅ |
generateSummary |
AI translation/summary | ✅ |
generateAnswer |
AI Q&A on document | ✅ |
| Command | Description |
|---|---|
npm run dev |
Start development server with hot reload |
npm run build |
Build for production |
npm run start |
Start production server |
npm run lint |
Run ESLint for code quality |
We welcome contributions! Here's how you can help:
- Fork the repository
- Clone your fork locally
- Create a feature branch:
git checkout -b feature/amazing-feature - Make your changes
- Test thoroughly
- Commit with a descriptive message:
git commit -m 'Add amazing feature' - Push to your branch:
git push origin feature/amazing-feature - Open a Pull Request
- We use TypeScript strict mode
- Follow the existing code patterns
- Use functional components with hooks
- Prefer Server Components where possible
- Write descriptive variable names (e.g.,
isLoading,hasError) - Use CSS variables for theming (
bg-brand,text-muted-foreground) - Constants go in
src/constants/index.ts— no magic numbers inline
Follow conventional commits:
feat: add document export feature
fix: resolve cursor sync delay
docs: update README with new env vars
refactor: simplify auth flow
Hydration mismatch errors
This can happen with components using browser APIs. Wrap them in ClientOnly:
import ClientOnly from "@/components/ClientOnly";
<ClientOnly fallback={<Skeleton />}>
<BrowserOnlyComponent />
</ClientOnly>;Liveblocks authentication fails
- Check that
LIVEBLOCKS_PRIVATE_KEYis set correctly - Verify you are signed in (Header should show your avatar + "Sign Out")
- Check the auth endpoint logs at
/api/auth-endpoint - Ensure the user has a room entry in Firestore (
users/{uid}/rooms/{docId})
Firebase permission denied
Deploy the security rules from the project root:
- Copy
firestore.rulesto Firebase Console → Firestore → Rules - Copy
storage.rulesto Firebase Console → Storage → Rules
Or use Firebase CLI:
firebase deploy --only firestore:rules,storageSee firestore.rules and storage.rules for the full rule definitions.
AI features not working
- Ensure at least one AI provider API key is set in
.env.local - Check the browser console for errors
- Verify the selected model has a corresponding API key configured
- For large documents, AI processing may take longer — streaming will show partial results
Debug ownership issues
Add ?debugOwner=1 to any document URL to enable detailed ownership logging in the browser console.
Server-side logging can be enabled with ROOM_OWNERSHIP_DEBUG=1 in .env.local.
This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0) — see LICENSE.md for the full license text.
Note: AGPL includes network-use/source-availability requirements when you run modified versions as a service. If you're unsure how this impacts your use case, please review the license terms.
- BlockNote — Amazing open-source editor
- Liveblocks — Real-time infrastructure
- Vercel AI SDK — Unified AI interface
- Shadcn UI — Beautiful UI components
- Firebase — Authentication and database
Made with ❤️ by the Spaces team