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
- One process to run and monitor
- SQLite file contains code/content, filesystem has assets
- Simple backups (database + storage in
pb_datadirectory) - 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
- 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
- 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
- Fetches block HTML/CSS/JS from database
- Injects content variables from fields
- Compiles in browser worker (parallel processing)
- Generates static HTML for each page
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 settingssite_symbols: Block code (HTML, CSS, JS)site_symbol_fields: Field definitions for blockssite_symbol_entries: Field values/contentpage_types: Page type configurationspages: Individual pagespage_sections: Block instances on pagespage_section_entries: Content for block instances
- 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
- For each page, fetch content from database
- Inject content into compiled blocks
- Render to static HTML
- Upload pages
- 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
- 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
- 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
- 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
- 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
- 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
- Use one active Primo process for a SQLite data directory
- Keep
pb_dataon 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
- 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
- Golang
- PocketBase
- SQLite 3
- Vite
Next Steps
Installation
Set up Primo for development or production.
Core Concepts
Understand Primo’s mental model.