# How Instatic Templates and Layouts Use Outlet/Insertion Patterns

> Discover how Instatic Templates and Layouts leverage outlet/insertion patterns for sophisticated component nesting. Explore dual-outlet architecture for seamless content injection & reusable slots.

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

---

**Instatic implements a dual-outlet architecture where `base.outlet` handles page-to-layout content injection and `base.slot-outlet` paired with `base.slot-instance` manages reusable component slots, enabling nested template composition without orphaned placeholders.**

Instatic's open-source static site generator employs a node-based template system that separates content structure from presentation through deterministic insertion mechanisms. The framework provides distinct outlet patterns for page-to-layout injection versus visual component slotting, each governed by specific modules in the `src/core` directory.

## The `base.outlet` Pattern for Page Content Injection

Instatic enforces a **single outlet per document** rule for core templates. This constraint ensures that when a page renders inside a layout, the content has exactly one canonical insertion point, eliminating ambiguity regarding where body content should appear.

### Detecting Outlets in Template Trees

The utility functions in [`src/core/templates/outlet.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/outlet.ts) provide the canonical detection logic used during template composition. The `firstOutletId(tree)` function traverses the node map and returns the first node where `moduleId` equals `'base.outlet'`. Complementary functions `treeHasOutlet(tree)` and `subtreeHasOutlet(rootId, tree)` determine outlet existence at any level of the document tree.

These helpers enable the composer to validate that a host template can accept content before attempting insertion. If no outlet exists, the system treats the template as terminal, gracefully returning the inner document unchanged rather than throwing composition errors.

### Template Composition with `composeTemplateChain`

The `composeTemplateChain` function in [`src/core/templates/templateCompose.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/templateCompose.ts) orchestrates the merge operation when nesting documents. The algorithm follows a strict replacement protocol:

1. **Identify** the outlet ID using `firstOutletId(host)` on the host template
2. **Locate** the specific node via an internal `locate(host, outletId)` call
3. **Delete** the outlet node entirely using `delete merged[outletId]`
4. **Splice** the inner document's nodes into the host at the outlet's former position

This **outlet replacement** strategy ensures the final merged tree contains only the layout's structural nodes and the page's content nodes. The `base.outlet` placeholder is completely removed during composition, leaving no traces in the static output.

### Dynamic Content Binding

Outlets automatically receive content through the binding system defined in [`src/core/templates/dynamicBindings.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/dynamicBindings.ts). During the rendering phase, the `base.outlet` node's `html` property automatically populates with the current entry's markdown body. This implicit binding eliminates manual content assignment, allowing authors to declare an outlet once while the framework handles data injection transparently.

## The Slot Outlet Pattern for Visual Components

Visual Components (VCs) utilize a different insertion mechanism based on **slot outlets** and **slot instances**. This pattern supports multiple named insertion points per component, unlike the single `base.outlet` restriction imposed on page templates.

### Declaring Slots with `base.slot-outlet`

Components define available slots by including `base.slot-outlet` nodes in their tree structure, as declared in [`src/modules/base/slotOutlet/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/slotOutlet/index.ts). Each outlet requires a `slotName` property to identify the insertion point, validated through the prop guards in [`src/core/visualComponents/propGuards.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/propGuards.ts).

These outlets function as persistent placeholders that survive initial tree parsing, waiting to be filled during the instantiation phase rather than being replaced during template composition.

### Slot Synchronization via [`slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/slotSync.ts)

When a VC reference (`base.visual-component-ref`) enters the canvas, the system generates matching content containers through [`src/core/visualComponents/slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/slotSync.ts). The `collectSlotOutlets(vcTree)` function performs a depth-first pre-order traversal to gather all `base.slot-outlet` nodes within the component definition. For each outlet discovered, the system creates a corresponding `base.slot-instance` node (defined in [`src/modules/base/slotInstance/index.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/modules/base/slotInstance/index.ts)) as a child of the VC reference, preserving the identical `slotName` property.

These slot instances serve as containers for author-authored content that eventually replaces the outlets during the rendering expansion phase.

### Instantiation and Outlet Expansion

The `instantiateVCAtRef` function in [`src/core/visualComponents/instantiate.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/instantiate.ts) executes the final outlet-to-content substitution:

1. Apply prop overrides to the Visual Component
2. Locate each `base.slot-outlet` node within the component tree
3. Retrieve the matching `base.slot-instance` by comparing `slotName` values
4. Replace the outlet's position in the tree with the instance's `children` nodes

If no slot instance exists for a given outlet (indicating an empty slot), the outlet remains as a placeholder that renders nothing. This prevents structural corruption while maintaining clear component boundaries.

## Practical Outlet Implementation Examples

The following TypeScript structures demonstrate the outlet definitions used by the Instatic core:

```typescript
// 1️⃣ A simple page template (has one outlet)
const pageTemplate = {
  id: 'tmpl_page',
  nodes: {
    root: { moduleId: 'base.page', children: ['outlet'] },
    outlet: { moduleId: 'base.outlet' }   // ← content will be inserted here
  }
};

// 2️⃣ A layout component that defines a slot
const twoColumnLayout = {
  id: 'layout_two_col',
  nodes: {
    root: { moduleId: 'base.visual-component-ref', children: ['slotOutlet'] },
    slotOutlet: { moduleId: 'base.slot-outlet', props: { slotName: 'main' } }
  }
};

// 3️⃣ Editor creates a slot instance when the layout is dropped
const slotInstance = {
  id: 'slotInst_1',
  moduleId: 'base.slot-instance',
  props: { slotName: 'main' },
  children: ['userContent']   // author‑added nodes that fill the slot
};

// 4️⃣ Rendering – the outlet is replaced by the instance’s children
// (handled by src/core/visualComponents/instantiate.ts)

```

## Publishing and Rendering Behavior

During the publishing phase, [`src/core/publisher/renderVisualComponentRef.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/renderVisualComponentRef.ts) processes any remaining `base.slot-outlet` nodes that survived instantiation. Empty slots render no output, producing zero HTML rather than placeholder comments or empty divs. This termination behavior ensures that unfinished templates gracefully degrade without leaving dangling artifacts in the final static files.

The system maintains this philosophy across both outlet types: `base.outlet` is deleted and replaced during composition, while `base.slot-outlet` either expands to consume its instance content or vanishes entirely during rendering.

## Summary

- **Single Outlet Invariant**: [`src/core/templates/outlet.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/outlet.ts) enforces one `base.outlet` per document via `firstOutletId()` and `treeHasOutlet()` utilities
- **Destructive Composition**: `composeTemplateChain()` in [`src/core/templates/templateCompose.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/templateCompose.ts) completely deletes the outlet node before splicing inner content into the host tree
- **Automatic Hydration**: The dynamic bindings system auto-fills `base.outlet` with markdown body content without explicit data wiring
- **Component Slots**: Visual Components use `base.slot-outlet` (placeholder) and `base.slot-instance` (content container) pairs defined in `src/modules/base/`, synchronized via [`src/core/visualComponents/slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/slotSync.ts)
- **Multi-Slot Support**: Components support multiple named slots identified by unique `slotName` properties, processed through depth-first traversal
- **Graceful Degradation**: Empty slots render no output, and templates without outlets return content unchanged rather than throwing errors

## Frequently Asked Questions

### What is the difference between `base.outlet` and `base.slot-outlet` in Instatic?

`base.outlet` serves as the sole insertion point for page content within a layout template, automatically binding to the entry's markdown body and replaced entirely during the `composeTemplateChain` operation. `base.slot-outlet` functions as a named placeholder within Visual Components that accepts specific content blocks through corresponding `base.slot-instance` nodes, enabling reusable components with several customizable regions that persist through the editing lifecycle.

### How does Instatic handle templates that contain no outlets?

When `treeHasOutlet()` returns false during composition, the system treats the host template as terminal and returns the inner document unchanged. This graceful degradation, implemented in [`src/core/templates/templateCompose.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/templateCompose.ts), allows unfinished or purely structural templates to exist in the hierarchy without breaking the rendering pipeline or requiring placeholder content.

### Can a Visual Component define multiple slots for different content areas?

Yes. Visual Components support multiple simultaneous slots by including several `base.slot-outlet` nodes, each with a unique `slotName` property. The `collectSlotOutlets()` function in [`src/core/visualComponents/slotSync.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/visualComponents/slotSync.ts) discovers all slots through depth-first traversal, and `instantiateVCAtRef()` expands each outlet independently using its matching `base.slot-instance` children, supporting complex layouts with distinct header, sidebar, and footer injection points.

### What happens to slot outlets that have no content assigned?

During instantiation, unpopulated slots retain their `base.slot-outlet` node in the tree, but the publisher in [`src/core/publisher/renderVisualComponentRef.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/publisher/renderVisualComponentRef.ts) renders them as nothing—producing no HTML output. This prevents empty placeholder artifacts in the final static files while maintaining the component's structural integrity during the editing phase.