Axelot
A modern, fast, and secure collaborative document/story platform
Overview
I started developing Axelot because I wanted a collaborative document/story platform that actually feels modern, fast, and secure. I was initially inspired by how https://gist.github.com/ works. Most existing tools either lack real-time editing features, have clunky authentication, or don’t scale well to edge runtimes.
The goal was to combine Next.js, Firebase, and Yjs to build something that s both powerful and future-proof. Many features in this project were ported from Kanjou and adjusted to be compatible with Material UI (MUI).
Development Journey
- Auth Integration. Get Auth.js working with Firebase custom tokens — ensuring token generation and verification flow is secure and stateless.
- Edge Compatibility. Refactor services so everything is stateless and safe for Vercel Edge (no in-memory session state, short-lived tokens, and signed URLs when needed).
- Collaboration. Integrate Yjs and WebRTC for low-latency peer sync; use Firestore for signaling and persistence.
- Theming & Accessibility. Use MUI theming and focus states to ensure an accessible editor experience.
- Testing & Validation. Batch file edits, lint checks, and manual UI validation across device sizes and network conditions.
Server-rendered non-user-specific sections
A practical example of when server rendering helps is the project dashboard. Some sections (for example, a list of recently published stories) are global and do not depend on the currently-authenticated user. Rendering these parts on the server avoids an unnecessary client round-trip and reduces perceived load time.
Server-rendered approach (before / after):
The DashboardShell can render the recentlyAdded list directly and mount lightweight client-only widgets for personalized sections (notifications, user drafts, presence) after the shell is visible.
Client-side hydration for user-specific widgets:
Why this helps:
- The server-rendered
recentlyAddedlist is cached at the edge, so all clients receive HTML immediately and avoid an extra client fetch. - Personalized widgets remain client-only, so sensitive data or session-bound requests are still retrieved securely on the client.
How Authorization Works
Authentication begins with Auth.js (NextAuth-style flows) to support OIDC and social providers. After sign-in we mint a Firebase custom token server-side and hand it to the client. The client exchanges the custom token for a Firebase SDK session and uses the UID provided by that token for Firestore access.
All Firestore rules reference request.auth.uid so reads/writes are validated at the database layer. Session handling is stateless and compatible with Edge runtimes (no in-memory caches or long-running processes required).
Collaboration in Real-Time
Yjs handles CRDT state for documents and presence (awareness). WebRTC is used for peer-to-peer replication between collaborators, while Firestore is used for signaling (offer/answer exchange). All updates are encrypted on the client and merged using Yjs, so conflicts are deterministic and predictable.
We use y-protocols/awareness to sync cursors and presence metadata (name, color, current selection). The system falls back to Firestore realtime updates when WebRTC peers cannot be established.