Skip to main content
Understanding Primo’s architecture helps with self-hosting, troubleshooting, and contributing to the project.

System Overview

Single Application:
  • SvelteKit frontend + PocketBase backend, all in one binary
  • SQLite database stores code and content
  • Files (images, uploads) stored in filesystem (or optional S3)
  • No microservices, no separate database server
  • Minimal operational overhead
What This Means:
  • One process to run and monitor
  • SQLite file contains code/content, filesystem has assets
  • Simple backups (database + storage in pb_data directory)
  • Easy to reason about and debug

Data Flow

Development Workflow

Publishing Workflow

CLI workspaces and database sync

The visual CMS stores records in PocketBase. The CLI exports those records as YAML, Svelte, and media files in a workspace. primo dev imports file changes, or writes browser changes back to files depending on its author mode. primo pull records a server revision alongside exported source. primo push checks that revision before replacing hosted CMS data. A successful push does not publish the site; publication is a separate browser action.

Key Components

PocketBase

Role: Backend, database, auth, file storage, realtime
  • SQLite database management
  • User authentication and permissions
  • File uploads and serving (local filesystem and optionally S3)
  • Realtime subscriptions for live updates
  • Custom API routes
Files:
  • Database: pb_data/data.db
  • Storage: pb_data/storage/
  • Backups: pb_data/backups/
PocketBase can be configured to use S3-compatible storage (AWS S3, Cloudflare R2, etc.) instead of local filesystem for uploaded files. This is useful for scaling.

Svelte Compiler

Role: Transforms block code into production JavaScript Where it runs:
  • Browser publishing and previews: Web Workers in the editor
  • CLI static builds: Node.js on the machine running primo build
  • Rollup worker for JavaScript bundling
  • PostCSS worker for CSS processing
When it runs:
  • On publish: Compiles all blocks to static files
  • In preview: Compiles blocks for live preview
  • Content changes refresh the editor preview; publishing regenerates pages from saved content
Process:
  1. Fetches block HTML/CSS/JS from database
  2. Injects content variables from fields
  3. Compiles in browser worker (parallel processing)
  4. Generates static HTML for each page
Browser publishing compiles Svelte on the editor’s device. The server still processes uploads, database writes, and static-file generation. CLI builds use the developer’s machine instead.

CodeMirror

Role: Code editor for developers Features:
  • Syntax highlighting for HTML, CSS, JavaScript
  • Autocompletion for field variables
  • Error detection
  • A single Svelte code pane with formatting, save, and preview shortcuts

TipTap

Role: Rich text editor for content Features:
  • Rich text editing with a formatting toolbar
  • Markdown shortcuts
  • Paste from Word/Google Docs
  • Link editing

File Structure

Database Schema

Key Collections:
  • sites: Site configuration and settings
  • site_symbols: Block code (HTML, CSS, JS)
  • site_symbol_fields: Field definitions for blocks
  • site_symbol_entries: Field values/content
  • page_types: Page type configurations
  • pages: Individual pages
  • page_sections: Block instances on pages
  • page_section_entries: Content for block instances
Why This Structure:
  • Blocks (symbols) are reusable across pages
  • Content (entries) is separate from code (symbols)
  • Pages reference blocks via sections
  • Supports multi-locale content (entries have locale field)

How Publishing Works

Static Site Generation

Step 1: Compile blocks
  • Fetch all blocks used on the site
  • Compile each block’s Svelte code
  • Generate optimized JavaScript bundles
  • Upload bundles
Step 2: Generate pages
  • For each page, fetch content from database
  • Inject content into compiled blocks
  • Render to static HTML
  • Upload pages
Step 3: Update static site
  • Copy new and updated JavaScript bundles to _symbols/
  • Copy new and updated media files to _uploads/
  • Copy new and updated pages to [page]/index.html
  • Clean up unused files
Result:
  • Static HTML files for every page
  • Shared JavaScript bundles
  • Fast, CDN-friendly output
  • Published page content is served statically; domain routing can still query the site record

Preview vs Published

Preview (in editor):
  • Compiles blocks on-demand
  • Shows unsaved changes
  • Uses iframe for isolation
  • Recompiles and refreshes iframe content as code or content changes
Published (live site):
  • Pre-compiled static files
  • No build step on request
  • Served directly from file system
  • Fast, cacheable responses

Realtime Updates

Primo uses PocketBase’s realtime subscriptions for live collaboration: What syncs in realtime:
  • Block code/content changes
  • Page structure updates
  • User activity indicators
  • Field value updates
How it works:
  • Server-sent events (SSE) connection to PocketBase
  • Receive updates as they happen
  • UI updates automatically

Performance Characteristics

Database:
  • SQLite is fast for read-heavy workloads
  • Use PocketBase backups, or stop the application before copying SQLite files
  • No network latency (in-process database)
  • Capacity depends on workload, storage, and server resources; measure your deployment
Publishing:
  • Browser publishing generates the site’s pages from saved data
  • Build time depends on blocks, pages, browser/device, and network speed
  • Compilation and uploads can run in parallel
  • Static output avoids rendering page content for every request
Editor:
  • CodeMirror handles large files well
  • Preview compilation is cached
  • Realtime updates are throttled
  • UI stays responsive during saves

Self-Hosting Considerations

System Requirements:
  • Docker
  • 1GB RAM minimum
  • Persistent disk storage
Scaling:
  • Use one active Primo process for a SQLite data directory
  • Keep pb_data on persistent storage
  • Put published output behind a CDN when useful, while bypassing admin and API traffic
  • S3-compatible upload storage is separate from the SQLite database and needs its own backup policy
Backups:
  • SQLite file contains code/content (pb_data/data.db)
  • Storage directory contains uploaded files (pb_data/storage/)
  • Backup both database + storage directory
  • Or create and download backup from PocketBase
  • If using S3 for uploads, back up the database and the external media bucket; PocketBase backup ZIPs exclude S3-uploaded files

Technology Stack Summary

Frontend:
  • SvelteKit 2
  • Svelte 5 (with runes)
  • TailwindCSS
  • CodeMirror 6
  • TipTap 3
Backend:
  • Golang
  • PocketBase
  • SQLite 3
Build Tools:
  • Vite

Next Steps

Installation

Set up Primo for development or production.

Core Concepts

Understand Primo’s mental model.