Skip to content

Documentation

Store System Architecture

Complete reference for SveltyCMS client-side stores — patterns, purpose, and when to use page.data instead.

6/25/2026
7 min read Edit on GitHub

SveltyCMS uses Svelte 5 runes ($state, $derived, $effect) for all client-side state. Stores are organized by domain — each file owns one concept. Server data flows through SvelteKit’s page.data (from +layout.server.ts), not through global stores.


1. Core Principle: page.data over Stores

SvelteKit injects server data into every component via page.data. Do not duplicate this into a store.

<!-- ✅ CORRECT: Derive from page data -->
<script>
  let { data } = $props();
  const avatarUrl = $derived(data.user?.avatar ?? '/Default_User.svg');
</script>

<!-- ❌ WRONG: Sync page data into a store, then read the store -->
<script>
  import { avatarSrc } from '@stores/store.svelte';
  // This adds a manual sync step, a store file, and 0 value
</script>

Use a store only when:

  • The value changes without page navigation (user toggle, WebSocket event, timer)
  • Multiple unrelated components need to react to the same change
  • The value is genuinely client-only (viewport size, canvas state, form draft)

Use page.data when:

  • The value comes from the server (user data, settings, collections)
  • Changes always trigger SSR navigation (language switch, route change)
  • Only descendant components of a layout need the value

2. Store Directory

Core Application

Store Type Purpose
store.svelte.ts App singleton + re-exports Minimal AppStorelistboxValueState, tabSetState, shouldShowNextButton, saveLayerStore. Re-exports from domain stores. No wrapper objects.
locale-store.svelte.ts Reactive wrappers systemLanguage, contentLanguage, translationProgress — bridges server cookies → Paraglide runtime
user-store.svelte.ts Utility normalizeAvatarUrl() — pure function, not a store. Avatar URL comes from page.data.user?.avatar.
validation-store.svelte.ts Singleton class Per-field form validation errors, isValid derived
data-change-store.svelte.ts Singleton class JSON snapshot comparison for unsaved changes detection
Store Type Purpose
global-settings.svelte.ts Singleton + SSE Public environment settings (SITE_NAME, PKG_VERSION). Proxy-based publicEnv for reactive access.
screen-size-store.svelte.ts Singleton class Viewport dimensions (width, height), breakpoint queries (isMobile, isTablet, isDesktop)
ui-store.svelte.ts Singleton class Panel visibility (leftSidebar, rightSidebar, pageheader, etc.). Route context + screen-size driven.
theme-store.svelte.ts Module-level $state Dark/light/system color scheme. Cookie persistence. OS preference listener.

Feature Stores

Store Type Purpose
collection-store.svelte.ts Singleton class Active collection context — schema, mode (view/edit/create), entry value, content structure
content-registry.svelte.ts Singleton class Compiled content tree — ContentNode hierarchy and derived Schema catalog. Multi-tenant.
mode-transition-guard.svelte.ts State machine Validates mode transitions (blocks edit→view with unsaved changes). Consolidated setMode() + transitionTo().
status-store.svelte.ts Service + $state Publish/unpublish toggle with API calls, debounce, and toast notifications
widget-store.svelte.ts Singleton class Widget registry — core/custom/marketplace widgets, dependency analysis, active set management
Store Type Purpose
image-editor-store.svelte.ts Factory function Canvas state — zoom, rotation, filters, crop, history stack, compare slider
collaboration-store.svelte.ts Singleton class Real-time WebSocket — activity stream, chat messages, room management via svelte-realtime
loading-store.svelte.ts Singleton class Global loading state with priority queue, timeouts, progress tracking, and analytics
toast.svelte.ts Singleton class Enterprise toast notification system with responsive positioning, pause-on-hover, flash messages
setup-store.svelte.ts Factory function Setup wizard form state with localStorage persistence, validation, and step management

User Preferences & Personalization

Store Type Purpose
user-prefs-overlay.svelte.ts Optimistic overlay Per-user density/variant/accessibility overrides. Apply immediately, sync to server async.
dashboard-preferences.svelte.ts Server-persisted Dashboard widget layout preferences (/api/system-preferences)
pinned-store.svelte.ts localStorage + $state User-pinned collections and media folders in sidebar
consent-store.svelte.ts localStorage + $state GDPR cookie consent preferences (analytics, marketing)

UI Micro-State

Store Type Purpose
active-input-store.svelte.ts Singleton class Currently focused token input (for TokenPicker insertion)
settings-config-state.svelte.ts SvelteSet Tracks which settings groups need configuration (empty required fields)
plugin-workspace.svelte.ts URL-synced $state Active plugin workspace overlay — ?plugin=<id> search param

System Health (system/)

File Type Purpose
state.svelte.ts Reactive container System state machine (IDLEREADYDEGRADED), service health tracking
types.ts Types SystemState, ServiceHealth, ServicePerformanceMetrics, anomaly detection types
config.ts Constants Baseline times, anomaly thresholds, initial state objects
metrics.ts Functions Performance tracking, anomaly detection, self-calibration, metric persistence
reporting.ts Functions Health check reports, bottleneck identification, timeout recommendations
async.ts Async utilities waitForSystemReady(), waitForServiceHealthy() with AbortSignal and intelligent timeouts

3. Store Patterns

Pattern A: Singleton Class with $state

Used by: ui-store, screen-size-store, collection-store, collaboration-store, loading-store

class MyStore {
  value = $state<string>("default");

  get computed(): boolean {
    return this.value !== "default";
  }

  update(v: string) {
    this.value = v;
  }
}

export const myStore = new MyStore();

When to use: Cross-component shared state that changes without page navigation.

Pattern B: Module-level $state

Used by: theme-store, toast

const state = $state({ dark: true });

export const myStore = {
  get isDark() {
    return state.dark;
  },
  toggle() {
    state.dark = !state.dark;
  },
};

When to use: Simpler than a class, no inheritance needed, fewer than 5 methods.

Pattern C: localStorage + $state

Used by: pinned-store, consent-store

class MyStore {
  items = $state<string[]>([]);

  constructor() {
    if (browser) this.load();
  }

  private load() {
    const stored = localStorage.getItem("key");
    if (stored) this.items = JSON.parse(stored);
  }

  private save() {
    localStorage.setItem("key", JSON.stringify(this.items));
  }
}

When to use: User preference that persists across sessions, no server round-trip needed.

Pattern D: Optimistic Overlay

Used by: user-prefs-overlay

class MyOverlay {
  #optimistic = $state<Prefs | null>(null);

  apply(prefs: Prefs) {
    this.#optimistic = { ...this.#optimistic, ...prefs };
  }
  release() {
    this.#optimistic = null;
  }
  getEffective(server?: Prefs) {
    return { ...server, ...this.#optimistic };
  }
}

When to use: Instant UI feedback before server confirms. Server becomes authoritative on next data fetch.

Pattern E: State Machine

Used by: mode-transition-guard

class MyStateMachine {
  private transitions = [{ from: "a", to: "b", validate: () => this.check() }];

  async transitionTo(state: State): Promise<boolean> {
    // Validate, run hooks, perform transition
  }
}

When to use: Complex state with guarded transitions (unsaved changes, permission checks).

Pattern F: URL-Synced $state

Used by: plugin-workspace

class MyUrlStore {
  value = $state<string | null>(readFromUrl());

  set(v: string) {
    this.value = v;
    history.pushState({}, "", `?param=${v}`);
  }
}

When to use: State that should survive page reloads and be shareable via URL.


4. Anti-Patterns to Avoid

❌ Duplicating page.data into a store

// BAD: Manual sync function copies server data into store
function syncUserToStore(user: User) {
  app.avatarSrc = user.avatar; // ← unnecessary
}

// GOOD: Components read page.data directly
const avatarUrl = $derived(data.user?.avatar ?? "/Default_User.svg");

❌ God Object stores

// BAD: One file exports 5 unrelated classes
// store.svelte.ts used to export AppStore, ValidationStore, DataChangeStore,
// translationProgress, avatarSrc, normalizeAvatarUrl, toast, tabSet...

// GOOD: Each domain has its own file
// locale-store, validation-store, data-change-store, user-store

❌ Stale snapshot exports

// BAD: Primitive export loses reactivity
export const isLoading = store.isLoading; // ← always false

// GOOD: Reactive getter preserves reactivity
export const isLoading = {
  get value() {
    return store.isLoading;
  },
};

❌ Server-only modules importing UI stores

// BAD: DB adapter imports dashboard preferences
import { preferences } from "@stores/dashboard-preferences.svelte";

// GOOD: DB adapters stay in the data layer

5. File Count Summary

Category Count
Core application 9
Feature stores 8
User preferences 4
UI micro-state 3
System health (system/) 6
Total 30

Related

architecturestoresstate-managementsvelte-5page-data
Was this page helpful?