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:
-
API Retrieval: The CMS handler
GET /admin/api/cms/sitereturns only the shell (excluding pages and visual components). It delegates parsing tovalidateSiteinsrc/core/persistence/validate.ts. -
Tolerant Parsing:
validateSiteinvokesparseSiteDocumentto parse the raw JSON. This function validates required fields while leniently accepting partial optional data. -
Post-Check Normalization: After parsing,
runShellPostChecks(lines 405-436 insrc/core/persistence/validate.ts) enforces cross-cutting invariants:normalizeSiteFiles– filters invalid file entries.normalizeSitePackage– ensures a validpackageJsonstructure.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
}
- Database Conversion: The repository in
server/repositories/site.tsprovidesshellToStorageandreadStoredShellto convert between the TypeScriptSiteShelltype 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
sitetable, deliberately excluding page content, visual components, and layouts to optimize persistence. - Schema validation relies on TypeBox via
SiteShellSchemainsrc/core/page-tree/siteDocument.ts, withparseSiteDocumentproviding tolerant parsing that throws only for required field violations. - The validation pipeline in
src/core/persistence/validate.tsenforces data integrity throughrunShellPostChecks, which normalizes files, packages, runtime configs, and color tokens. - Architectural boundaries prevent content pollution of the shell, enforced by the composite
SiteDocumenttype and theno-vc-in-site-shellarchitectural 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →