# How Visual Components and Slots Work in Instatic's Page Tree

> Learn how Instatic's visual components and page tree use named slots and slot instances for content management. Discover how syncSlotInstances keeps content in sync for publishing.

- Repository: [CoreBunch/Instatic](https://github.com/CoreBunch/Instatic)
- Tags: internals
- Published: 2026-07-28

---

**Visual Components in Instatic expose named slots via `base.slot-outlet` declarations, while the page tree automatically materializes locked `base.slot-instance` children to hold consumer content, kept in sync by the `syncSlotInstances` algorithm and rendered during publishing by matching slot names to their corresponding outlets.**

Instatic's page tree architecture treats Visual Components and slots as first-class primitives for building reusable page elements. When a content author drops a Visual Component onto a page, the system instantiates a `base.visual-component-ref` node that dynamically generates locked slot containers based on the component's internal slot definitions. This article examines the source code in `src/core/visualComponents/` to explain the synchronization logic, recursion guards, and runtime rendering flow that make this system work.

## Core Architecture: Visual Components and Slots

The relationship between a Visual Component and its slots involves three distinct node types living in different parts of the tree.

### The Component Reference Node (`base.visual-component-ref`)

A `base.visual-component-ref` node lives on the consumer's page and references a Visual Component definition by its `componentId`. It may also specify `propOverrides` to customize the component's behavior. This node acts as the anchor point where slot synchronization occurs.

### Slot Declarations (`base.slot-outlet`)

Inside the Visual Component definition itself, developers place `base.slot-outlet` nodes to mark named positions where consumer content should be injected. Each outlet declares a `slotName` property (defaulting to `"children"` if omitted) that links it to its corresponding instance during publishing.

### Slot Containers (`base.slot-instance`)

When a Visual Component reference is created or updated, Instatic automatically generates `base.slot-instance` nodes as **locked** children of the reference node. These instances hold the actual content provided by the page author and are synchronized to match the component's outlet definitions. Because they are locked, the UI prevents renaming, moving, or deleting these nodes directly, ensuring structural integrity.

## Synchronizing Slot Instances with Component Definitions

The synchronization engine lives in [`src/core/visualComponents/slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/slotSync.ts) and ensures that the children of a `base.visual-component-ref` always match the current slot definitions of the referenced component.

### Collecting Outlet Names

Before synchronizing, the system calls `collectSlotOutletNames` to walk the component definition tree and extract the set of declared slot names:

```typescript
export function collectSlotOutletNames(tree: { nodes: NodeMap; rootId: string }): Set<string>

```

This function scans the component's node map for all `base.slot-outlet` modules and returns a `Set<string>` containing the valid slot identifiers.

### The Sync Algorithm

The `syncSlotInstances` function (lines 158-236 in [`slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/slotSync.ts)) performs a four-phase diff operation to produce a **`SyncResult`** describing three possible operations:

- **Insert** – Schedule creation of a new locked `base.slot-instance` for a slot that exists in the component definition but lacks a corresponding instance on the reference.
- **Rename** – Update the `props.slotName` of an existing instance when the slot's name has changed in the component definition but the content should be preserved.
- **Delete** – Remove an instance and its entire subtree when the corresponding slot no longer exists in the component.

The algorithm first maps existing slot-instances by their current `slotName`, then identifies matches requiring renames, schedules deletions for orphaned instances, and finally schedules insertions for new slots.

### Applying Sync Results Atomically

Once the diff is computed, `applySlotSyncResult` (lines 267-313) mutates the page's node map in a specific order: **rename → delete → insert**. After applying these operations, it re-orders the parent reference node's `children` array to reflect the slot order defined in the component, ensuring visual consistency.

## Guarding Against Recursive Components

Visual Components may reference other Visual Components, creating potential for infinite recursion. The [`src/core/visualComponents/recursionGuard.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/recursionGuard.ts) module prevents this by checking the ancestry of a reference before instantiation. If a cycle is detected—where Component A references Component B which eventually references Component A again—the system throws a `VisualComponentRecursionError`, halting both editor rendering and publishing to prevent infinite loops.

## Instantiating Visual Components in the Editor

When a user drops a Visual Component onto the canvas, the editor invokes `instantiateVCAtRef` from [`src/core/visualComponents/instantiate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/instantiate.ts). This function:

1. Resolves the component definition tree using the `componentId` stored on the reference node.
2. Executes `syncSlotInstances` to materialize the required `base.slot-instance` children.
3. Flattens the component tree (including the new slot-instances) into a flat node map merged into the page's tree.

The instantiated content appears immediately in the editor via `VCInlineTree` ([`src/modules/base/visualComponentRef/VCInlineTree.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/visualComponentRef/VCInlineTree.tsx)), allowing authors to edit slot content in place.

## Publishing and Rendering Slot Fills

During static site generation, `renderVisualComponentRef` in [`src/core/publisher/renderVisualComponentRef.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/renderVisualComponentRef.ts) handles the final output:

1. It looks up the referenced component definition.
2. Builds a map of `base.slot-instance` nodes under the reference, keyed by `slotName`.
3. Walks the component's definition tree recursively; when encountering a `base.slot-outlet`, it retrieves the matching slot-instance's children and inlines them into the output.
4. The slot-outlet itself is transparent—it emits **no HTML wrapper**—so the final page contains only the consumer's content without structural artifacts.

## UI Constraints for Locked Slot Instances

The editor enforces strict constraints on `base.slot-instance` nodes to prevent accidental corruption of the component contract. In [`src/admin/pages/site/panels/DomPanel/LayerNodeContextMenu.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/panels/DomPanel/LayerNodeContextMenu.tsx), destructive actions like rename, delete, and reorder are disabled for these nodes. The `SlotInstanceEditor` ([`src/modules/base/slotInstance/SlotInstanceEditor.tsx`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/slotInstance/SlotInstanceEditor.tsx)) renders a header displaying the slot name and a container for editable children, while `SlotOutletEditor` renders only a thin placeholder in the component definition canvas.

## Summary

- **Visual Components** are referenced via `base.visual-component-ref` nodes that point to reusable component definitions.
- **Slots** are declared inside components using `base.slot-outlet` nodes with unique `slotName` properties.
- **Slot Instances** are auto-generated, locked `base.slot-instance` nodes that hold consumer content and synchronize automatically with the component's slot definitions.
- **`syncSlotInstances`** computes insert, rename, and delete operations to keep slot instances aligned with the component definition.
- **`applySlotSyncResult`** applies these changes atomically to maintain tree consistency.
- **Recursion guards** prevent cyclic component references from crashing the editor or build process.
- **Publishing** transparently injects slot content into outlet positions, emitting clean HTML without wrapper elements.

## Frequently Asked Questions

### How does Instatic prevent slot content from getting out of sync when a component is updated?

Instatic runs `syncSlotInstances` whenever a Visual Component reference is created or the component definition changes. This function compares the current set of `base.slot-outlet` names in the component against existing `base.slot-instance` children, automatically inserting new slots, renaming existing ones to match updated names, and deleting orphaned instances while preserving other content.

### Can users manually delete or rename a slot instance in the page tree?

No. `base.slot-instance` nodes are created with a `locked: true` flag that disables destructive actions in the DOM panel. Users can only edit the children inside the slot instance; they cannot move, rename, or delete the container itself. This ensures the slot hierarchy remains consistent with the component definition.

### What happens if a Visual Component references itself or creates a circular reference?

The recursion guard in [`src/core/visualComponents/recursionGuard.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/recursionGuard.ts) checks the ancestry chain before instantiating any Visual Component reference. If it detects a cycle—for example, Component A referencing Component B which references Component A—it throws a `VisualComponentRecursionError`, preventing both the editor and the publisher from entering an infinite loop.

### How is slot content rendered in the final static HTML?

During publishing, `renderVisualComponentRef` builds a map of slot instances by name, then walks the component definition tree. When it encounters a `base.slot-outlet`, it looks up the matching slot instance and renders its children in place of the outlet. The outlet node itself produces no HTML output, ensuring the final page contains only the author's content without extra wrapper elements.