# Instatic Site Shell Document: Real-Time Collaborative Editing Architecture

> Explore Instatic's site shell document architecture and real-time collaborative editing powered by Yjs CRDT for seamless multi-user website updates without conflicts.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: architecture
- Published: 2026-08-02

---

**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`](https://github.com/CoreBunch/Instatic/blob/main/src/core/persistence/validate.ts))
- **Mutated** only through a centralized, tree-agnostic mutation API ([`src/core/page-tree/mutations.ts`](https://github.com/CoreBunch/Instatic/blob/main/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:

1. **Load**: The admin interface fetches the current `siteShell` JSON via `apiRequest` and validates it against `SiteShellSchema`.
2. **Create Y-doc**: The client initializes a `Y.Doc` and loads the JSON into a `Y.Map` named `site`.
3. **Connect**: A WebSocket opens to `/admin/api/cms/site-socket`, handled by [`src/server/collab/siteSocket.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/server/collab/siteSocket.ts), which registers the document in the global `CollabStore`.
4. **Sync**: Yjs exchanges binary update messages over the socket, ensuring eventual consistency across all participants.
5. **Apply mutations**: Operations like `insertNode` or `updateNodeProps` dispatch through [`src/core/page-tree/mutations.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/page-tree/mutations.ts), which updates the Y-doc and triggers remote synchronization.
6. **Persist**: Saving a draft extracts the current JSON from the Y-doc and writes it back to `sites.siteShell` inside a transaction ([`src/server/repositories/siteRepository.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/server/repositories/siteRepository.ts)).
7. **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`](https://github.com/CoreBunch/Instatic/blob/main/src/core/persistence/validate.ts).

```typescript
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`](https://github.com/CoreBunch/Instatic/blob/main/src/server/collab/siteSocket.ts).

```typescript
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`](https://github.com/CoreBunch/Instatic/blob/main/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.

```typescript
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`](https://github.com/CoreBunch/Instatic/blob/main/src/server/repositories/siteRepository.ts), which ensures atomic writes to the `sites` table.

```typescript
// 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`](https://github.com/CoreBunch/Instatic/blob/main/src/core/page-tree/treeSchema.ts)** – Defines the `NodeTree` schema used by the site-shell.
- **[`src/core/page-tree/mutations.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/page-tree/mutations.ts)** – Central mutation API for insert, delete, and update operations.
- **[`src/core/persistence/validate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/persistence/validate.ts)** – TypeBox validation for incoming JSON at HTTP boundaries.
- **[`src/server/collab/siteSocket.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/server/collab/siteSocket.ts)** – WebSocket endpoint bridging Y-doc updates between clients and server.
- **[`src/server/collab/store.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/server/collab/store.ts)** – Global `CollabStore` managing active Y-doc lifecycles.
- **[`src/server/repositories/siteRepository.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/server/repositories/siteRepository.ts)** – Transaction-safe persistence to the `sites` table.
- **[`src/core/visualComponents/vcRefs.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/vcRefs.ts)** – Handles component references within the shell tree.
- **[`src/core/visualComponents/slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/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.siteShell` column 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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/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`](https://github.com/CoreBunch/Instatic/blob/main/src/core/page-tree/mutations.ts) enforce structural constraints, such as valid parent-child relationships and required slot configurations.