Instatic Site Shell Document: Real-Time Collaborative Editing Architecture
Instatic stores the entire website as a single JSON "site-shell" document in the database and uses Yjs CRDT to synchronize edits across multiple users in real time without conflicts.
The CoreBunch/Instatic repository implements a unified approach to content management by treating the whole website as one mutable site-shell document. This document serves as the single source of truth for every page, component, layout, and asset, enabling seamless collaborative editing through a lightweight, self-hosted architecture that runs entirely within a single Bun server.
What Is the Instatic Site Shell Document?
The site-shell is a JSON tree structure—formally defined as a NodeTree—that lives in the sites table under the siteShell column. It describes the complete hierarchy of visual components, their parameters, and slot instances across the entire website.
Because the shell represents the single source of truth, it is:
- Validated at the HTTP boundary using TypeBox schemas (
src/core/persistence/validate.ts) - Mutated only through a centralized, tree-agnostic mutation API (
src/core/page-tree/mutations.ts) - Synchronized in real time via Yjs CRDT layers (
src/server/collab/*)
When editors work in the visual admin interface, they are not modifying individual files or database rows. Instead, they update this shared document, which automatically propagates changes to all connected clients.
The Collaborative Editing Lifecycle
Instatic enables real-time collaboration through a seven-step pipeline that bridges the database, server memory, and connected browsers:
- Load: The admin interface fetches the current
siteShellJSON viaapiRequestand validates it againstSiteShellSchema. - Create Y-doc: The client initializes a
Y.Docand loads the JSON into aY.Mapnamedsite. - Connect: A WebSocket opens to
/admin/api/cms/site-socket, handled bysrc/server/collab/siteSocket.ts, which registers the document in the globalCollabStore. - Sync: Yjs exchanges binary update messages over the socket, ensuring eventual consistency across all participants.
- Apply mutations: Operations like
insertNodeorupdateNodePropsdispatch throughsrc/core/page-tree/mutations.ts, which updates the Y-doc and triggers remote synchronization. - Persist: Saving a draft extracts the current JSON from the Y-doc and writes it back to
sites.siteShellinside a transaction (src/server/repositories/siteRepository.ts). - Publish: A separate pipeline bakes static HTML from the committed shell, ensuring the live site only reflects saved states.
Loading and Validating the Site Shell
On initial load, the client retrieves and validates the site-shell document using type-safe API helpers defined in src/core/persistence/validate.ts.
import { apiRequest } from '@core/http';
import { SiteShellSchema } from '@core/schemas/siteShell';
export async function loadSiteShell() {
const siteShell = await apiRequest('/admin/api/cms/site-shell', {
schema: SiteShellSchema,
});
return siteShell; // typed as the validated JSON shape
}
This validation step ensures that the NodeTree structure conforms to the expected schema before entering the collaborative editing session.
Initializing Yjs for Real-Time Synchronization
Once loaded, the shell enters the collaborative layer through Yjs. The client creates a Y.Doc and connects to the server-side WebSocket endpoint managed by src/server/collab/siteSocket.ts.
import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';
import { loadSiteShell } from './loadSiteShell';
export async function initCollab() {
const json = await loadSiteShell();
// Create a Y-doc and import the JSON tree
const ydoc = new Y.Doc();
const yMap = ydoc.getMap('site');
yMap.set('root', json);
// Connect to the server-side Y-doc
const wsProvider = new WebsocketProvider(
`${location.origin.replace(/^http/, 'ws')}/admin/api/cms/site-socket`,
'site-doc',
ydoc,
);
// Listen for remote updates
yMap.observe(() => {
// React-state update, e.g. via Zustand
});
return { ydoc, wsProvider };
}
The WebsocketProvider bridges the client's Y-doc with the server's CollabStore, enabling binary patch streaming that guarantees all participants converge to the same state without manual conflict resolution.
Performing Tree Mutations
All structural changes to the site-shell flow through the mutation engine in src/core/page-tree/mutations.ts. This abstraction ensures that edits are applied consistently to both the local Y-doc and the remote synchronized state.
import { mutateActiveTree } from '@core/page-tree';
import { insertNode } from '@core/page-tree/mutations';
// Example: add a new Text block under the current selected node
function addTextBlock(selectedNodeId: string) {
mutateActiveTree((tree) => {
insertNode(tree, {
parentId: selectedNodeId,
type: 'base.text',
props: { content: 'New block' },
});
});
}
The mutateActiveTree wrapper handles the Yjs transaction boundaries, ensuring that every mutation automatically propagates to other collaborators as a discrete, reversible update.
Persisting Changes to the Database
When users save drafts, the server extracts the current JSON representation from the Y-doc and persists it transactionally. This process is handled by src/server/repositories/siteRepository.ts, which ensures atomic writes to the sites table.
// In src/server/handlers/cms/saveDraftSite.ts
import { readValidatedBody } from '@core/http';
import { SiteShellSchema } from '@core/schemas/siteShell';
import { siteRepository } from '../repositories/siteRepository';
export async function saveDraftSite(req) {
const { name, siteShell } = await readValidatedBody(req, {
type: 'object',
properties: {
name: { type: 'string' },
siteShell: SiteShellSchema,
},
required: ['name', 'siteShell'],
});
await siteRepository.saveDraft(name, siteShell);
}
This persistence layer separates draft state from published content. The live website is generated only when a publish pipeline bakes static HTML from the committed siteShell, ensuring visitors never see unsaved collaborative edits.
Key Implementation Files
The site-shell architecture and collaborative editing layer span these critical paths:
src/core/page-tree/treeSchema.ts– Defines theNodeTreeschema used by the site-shell.src/core/page-tree/mutations.ts– Central mutation API for insert, delete, and update operations.src/core/persistence/validate.ts– TypeBox validation for incoming JSON at HTTP boundaries.src/server/collab/siteSocket.ts– WebSocket endpoint bridging Y-doc updates between clients and server.src/server/collab/store.ts– GlobalCollabStoremanaging active Y-doc lifecycles.src/server/repositories/siteRepository.ts– Transaction-safe persistence to thesitestable.src/core/visualComponents/vcRefs.ts– Handles component references within the shell tree.src/core/visualComponents/slotSync.ts– Synchronizes slot-instance nodes to maintain structural integrity.
Summary
- The site-shell is a single JSON document stored in the
sites.siteShellcolumn that serves as the authoritative source of truth for the entire website structure. - Yjs CRDT powers real-time collaboration, enabling multiple users to edit simultaneously without locks or manual merges.
- All mutations route through
src/core/page-tree/mutations.ts, ensuring consistent application across local and remote states. - The architecture runs entirely within a single Bun server, eliminating external CMS dependencies or hidden caching layers.
- Drafts persist to the database via
siteRepository.ts, while a separate publish pipeline generates static HTML for the live site.
Frequently Asked Questions
How does Instatic handle editing conflicts between multiple users?
Instatic uses Yjs CRDT (Conflict-free Replicated Data Types) to automatically merge concurrent edits. When two users modify different parts of the site-shell simultaneously, Yjs exchanges binary update messages over WebSocket connections that guarantee eventual consistency without requiring manual conflict resolution or locking mechanisms.
Can editors work offline and sync changes later?
While the analysis focuses on real-time synchronization, the Yjs architecture inherently supports offline editing. Because the Y.Doc maintains a complete edit history locally, changes made while disconnected can theoretically be synchronized upon reconnection through the same WebsocketProvider mechanism, though the specific offline policy depends on the client implementation.
What makes the Instatic site-shell different from traditional headless CMS architectures?
Traditional headless CMS platforms often separate content into discrete entries or documents. Instatic treats the entire website as one document, storing it as a unified NodeTree JSON structure. This eliminates cross-reference complexity and enables the visual editor to manipulate the complete site hierarchy—pages, components, layouts, and assets—within a single collaborative session.
How is the site-shell validated for data integrity?
Validation occurs at multiple layers. The HTTP boundary uses TypeBox schemas defined in src/core/persistence/validate.ts to ensure incoming JSON conforms to SiteShellSchema before entering the mutation engine. Additionally, the tree-specific mutations in src/core/page-tree/mutations.ts enforce structural constraints, such as valid parent-child relationships and required slot configurations.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →