Understanding the Instatic Site Shell Configuration Model: Core Architecture and Schema Design

The Instatic site shell is the persisted top-level configuration stored in the site table that defines everything about a site except its page contents, visual components, and saved layouts, governed by the SiteShellSchema in src/core/page-tree/siteDocument.ts.

The Instatic site shell configuration model serves as the authoritative backbone of every site managed by the CoreBunch/Instatic repository. It encapsulates site-wide settings, responsive breakpoints, style rules, and runtime metadata while deliberately excluding content-heavy entities like pages and components. This architectural separation enables efficient persistence, rapid validation, and granular retrieval of critical site configuration without the overhead of loading entire page trees or visual component libraries.

What Is the Instatic Site Shell?

In the Instatic architecture, the site shell represents the complete persisted configuration for a specific site. It lives as a single row in the site database table and functions as the single source of truth for site-level metadata. Crucially, the shell explicitly excludes page contents, visual components, and saved layouts—these entities reside in separate rows (pages, components, layouts) and are merged with the shell only at runtime.

The shell's structure is defined by the SiteShellSchema in src/core/page-tree/siteDocument.ts, a TypeBox object that generates the corresponding TypeScript SiteShell type:

export const SiteShellSchema = SiteDocumentSchema
export type SiteShell = Static<typeof SiteShellSchema>

Core Schema Fields of the Site Shell

The SiteShell type enforces a strict contract through the following fields, each parsed with specific resilience semantics:

Field Type Description
id string Unique database identifier for the site.
name string Human-readable site name displayed in the admin UI.
breakpoints Breakpoint[] Responsive design breakpoints parsed by parseBreakpoint.
conditions ConditionDef[] (Optional) Reusable CSS conditions (@media, @container, @supports); invalid entries are dropped.
settings SiteSettings Site-wide defaults; missing values fall back to DEFAULT_SITE_SETTINGS from src/core/site-settings/defaults.ts.
styleRules Record<string, StyleRule> Registry of CSS style rules parsed tolerantly—malformed entries are silently ignored.
files SiteFile[] Site-level assets and scripts; each parsed by parseSiteFile with tolerance for malformed blobs.
explorer SiteExplorerOrganization Editor-specific metadata for the Site Explorer UI.
packageJson SitePackageJson Minimal package representation defaulting to { dependencies: {}, devDependencies: {} } if missing.
runtime SiteRuntimeConfig Runtime scripts and styles normalized by normalizeSiteRuntimeConfig.
createdAt / updatedAt number Unix timestamps marking creation and last update; required for persistence.

Resilience Semantics: The tolerant parser parseSiteDocument throws immediately on missing required fields (id, name, breakpoints, timestamps) but supplies safe defaults for optional sections like settings, packageJson, and runtime configuration.

Loading, Validation, and Persistence

The site shell follows a rigorous validation pipeline when fetched or stored:

  1. API Retrieval: The CMS handler GET /admin/api/cms/site returns only the shell (excluding pages and visual components). It delegates parsing to validateSite in src/core/persistence/validate.ts.

  2. Tolerant Parsing: validateSite invokes parseSiteDocument to parse the raw JSON. This function validates required fields while leniently accepting partial optional data.

  3. Post-Check Normalization: After parsing, runShellPostChecks (lines 405-436 in src/core/persistence/validate.ts) enforces cross-cutting invariants:

    • normalizeSiteFiles – filters invalid file entries.
    • normalizeSitePackage – ensures a valid packageJson structure.
    • normalizeSiteRuntimeBlock – fills missing script and style entries.
    • normalizeFrameworkColors – guarantees framework color tokens exist.
export function validateSite(raw: unknown): SiteShell {
  const shell = parseSiteDocument(raw);
  return runShellPostChecks(shell);   // https://github.com/CoreBunch/Instatic/blob/main/src/core/persistence/validate.ts#L405-L436
}
  1. Database Conversion: The repository in server/repositories/site.ts provides shellToStorage and readStoredShell to convert between the TypeScript SiteShell type and the database row format.

Architectural Separation: Site Shell vs. Site Document

The full in-memory representation, SiteDocument, extends the shell with content entities but never persists them together:

export type SiteDocument = SiteShell & {
  pages: Page[];
  visualComponents: VisualComponent[];
  layouts: SavedLayout[];
}   // https://github.com/CoreBunch/Instatic/blob/main/src/core/page-tree/siteDocument.ts#L102-L106

This separation is enforced architecturally by the test in src/__tests__/architecture/no-vc-in-site-shell.test.ts, ensuring the shell remains lightweight and content-agnostic.

Programmatic Examples

Creating a New Shell Programmatically

When bootstrapping sites in migrations or CLI tools, construct a plain object matching SiteShellSchema and pass it through the tolerant parser:

import { SiteShellSchema, parseSiteDocument } from '@core/page-tree';
import { Type } from '@core/utils/typeboxHelpers';

const rawShell = {
  id: 'site-123',
  name: 'My Blog',
  breakpoints: [{ name: 'mobile', width: 480, icon: 'mobile' }],
  settings: {},               // filled with defaults
  styleRules: {},
  files: [],
  explorer: {},
  packageJson: {},
  runtime: {},
  createdAt: Date.now(),
  updatedAt: Date.now(),
};

const shell = parseSiteDocument(rawShell);
console.log('Validated shell:', shell);

Updating the Shell via CMS API

Client-side admin tools can update configuration through the REST API, using the schema for response validation:

import { apiRequest } from '@core/http';

async function updateShell(newShell: Partial<SiteShell>) {
  const res = await apiRequest('/admin/api/cms/site', {
    method: 'PUT',
    body: { site: newShell },
    schema: SiteShellSchema,
  });
  console.log('Updated shell version:', res.site.updatedAt);
}

Accessing Shell Data in the Admin UI

React hooks in the admin interface consume the shell as typed data:

import { useAdminBoot } from '@/admin/preauth/useAdminBoot';
import { SiteShell } from '@core/page-tree';

function useSiteName() {
  const { shell } = useAdminBoot();   // `shell` is a `SiteShell`
  return shell?.name ?? 'Untitled';
}

Summary

  • The Instatic site shell is the central configuration object stored in the site table, deliberately excluding page content, visual components, and layouts to optimize persistence.
  • Schema validation relies on TypeBox via SiteShellSchema in src/core/page-tree/siteDocument.ts, with parseSiteDocument providing tolerant parsing that throws only for required field violations.
  • The validation pipeline in src/core/persistence/validate.ts enforces data integrity through runShellPostChecks, which normalizes files, packages, runtime configs, and color tokens.
  • Architectural boundaries prevent content pollution of the shell, enforced by the composite SiteDocument type and the no-vc-in-site-shell architectural test gate.

Frequently Asked Questions

What distinguishes the site shell from the full site document in Instatic?

The site shell contains only configuration metadata such as breakpoints, settings, and runtime flags, while the full site document represents the runtime merger of the shell with pages, visualComponents, and layouts. The shell persists independently in the database, whereas the full document is assembled in memory only when needed.

What happens if required fields are missing from the site shell during validation?

The tolerant parser parseSiteDocument throws a validation error for missing required fields including id, name, breakpoints, and the timestamp fields. However, for optional sections such as settings, packageJson, or runtime, the system applies default values—drawing from DEFAULT_SITE_SETTINGS or normalization functions rather than failing.

How are site-level files and runtime configuration handled in the shell?

The files array contains SiteFile objects representing scripts, styles, and assets, while the runtime field holds SiteRuntimeConfig specifying active scripts and style locks. During validation, normalizeSiteFiles filters malformed entries and normalizeSiteRuntimeBlock ensures missing entries are populated with safe defaults, allowing the shell to tolerate partial data ingestion.

Where is the site shell stored and how is it converted for database persistence?

The shell persists as a single row in the site table. Conversion between the TypeScript SiteShell type and the database format is handled by shellToStorage and readStoredShell in server/repositories/site.ts (lines 31-46), which manage serialization of complex nested objects like styleRules and explorer metadata.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →