# How Instatic Templates Handle Layouts with Outlets and Content Flow

> Learn how Instatic templates use base.outlet, resolveTemplateChain, and composeTemplateChain to manage layouts, outlets, and content flow for efficient website publishing.

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

---

**Instatic templates define reusable page layouts through a single `base.outlet` node that acts as the insertion point, merging template chains via `resolveTemplateChain` and `composeTemplateChain` to stream content into a composed tree for publishing.**

In the CoreBunch/Instatic static site generator, templates are specialized pages that provide chrome and structure for other content. According to the source code, the system treats layouts as normal page trees containing exactly one **`base.outlet`** node where matched content is spliced, creating a seamless flow from outer global wrappers to inner post-type-specific layouts.

## Template Resolution and the Matching Pipeline

The template system resolves applicable layouts in three distinct stages: matching, composition, and publishing. This pipeline ensures that content flows from the most general layout to the most specific, terminating with the actual page or entry content.

### Resolving the Template Chain with resolveTemplateChain

The entry point for layout selection is `resolveTemplateChain`, implemented in [`src/core/templates/templateMatching.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/templateMatching.ts). This function evaluates the site’s pages against a route context to determine which templates apply.

The matcher evaluates two breadth levels:

- **`everywhere`** – The outermost global layout that applies to all routes
- **`postTypes`** – The innermost layout targeting specific content types

For each level, the system selects the highest-priority template, using document order as a tiebreaker. The function returns an ordered array running **outer → inner** (`Page[]`), establishing the sequence in which layouts will wrap the content.

### Composing Layouts with composeTemplateChain

Once the chain is resolved, `composeTemplateChain` in [`src/core/templates/templateCompose.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/templateCompose.ts) performs the actual merging. The composer filters out any template lacking a `base.outlet` node, as these cannot accept content.

The composition process works from the inside out:

1. Starting with the innermost template, it **splices** the terminal page’s node tree into the outlet using `spliceIntoOutlet`
2. Each subsequent outer template wraps the current tree by splicing it into its own outlet
3. The merger **re-keys node IDs**, removes the outlet node itself, and **reindexes parent relationships** via `reindexNodeParents`

Because the outlet node is removed during this process, it never appears in the final rendered output—only its placeholder children are replaced by the actual content.

## The base.outlet Node and Content Flow Control

The outlet system relies on helpers centralized in [`src/core/templates/outlet.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/outlet.ts) to maintain strict invariants:

- **`firstOutletId`** – Returns the ID of the first outlet found, or `null` if absent
- **`treeHasOutlet`** – Reports whether a document contains any outlet node
- **`subtreeHasOutlet`** – Guards against duplicating subtrees that would introduce a second outlet

These utilities ensure that at most one outlet exists after composition. This guarantee allows the editor UI to safely expose a single **Content Outlet** tile in the module picker, as documented in [`docs/features/templates.md`](https://github.com/CoreBunch/Instatic/blob/main/docs/features/templates.md) and implemented in [`src/admin/pages/site/module-picker/moduleInserterModel.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/module-picker/moduleInserterModel.ts). The picker disables the tile for non-template pages or for templates that already contain an outlet.

## Publishing the Final Composed Tree

After composition, the merged tree is passed to the publisher, which treats it identically to any regular page tree. The absence of the `base.outlet` node in the final structure means the rendered HTML consists solely of the layout’s chrome—headers, footers, and sidebars—plus the page-specific body content that flowed through the outlet chain.

## Practical Implementation Examples

The following examples demonstrate the complete workflow from matching to publishing:

```typescript
// Resolve the template chain for a public route.
// For a post entry with slug "my-post", the context specifies the entry kind and table.
import { resolveTemplateChain } from '@core/templates';
import { site } from './exampleSite';

const ctx = { kind: 'entry', tableSlug: 'posts' } as const;
const chain = resolveTemplateChain(site, ctx);
// chain → [globalLayoutPage, postTypeLayoutPage]

```

```typescript
// Compose the chain with the terminal page holding the actual content.
import { composeTemplateChain } from '@core/templates';
import { aboutPage } from './pages';

const merged = composeTemplateChain(chain, { kind: 'page', page: aboutPage });
// merged.nodes contains the layout + content, with the outlet replaced

```

```typescript
// Publish the merged page (the publisher treats it as a normal page).
import { publishPage } from '@core/publisher';
await publishPage(merged);

```

## Summary

- **Templates are pages** that define layouts through a single `base.outlet` node acting as a content insertion point
- **`resolveTemplateChain`** selects applicable layouts by evaluating `everywhere` and `postTypes` breadth levels, returning an ordered array from outer to inner
- **`composeTemplateChain`** merges templates by splicing content into outlets, re-keying node IDs, and removing the outlet node so it never reaches the final HTML
- **Outlet helpers** in [`src/core/templates/outlet.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/outlet.ts) enforce the invariant of at most one outlet per composed tree, enabling UI gating in the module picker
- The **publisher** receives a clean, composed page tree identical to standard pages, with content flowed through the entire template hierarchy

## Frequently Asked Questions

### What happens if a template in the chain lacks a base.outlet?

The composer ignores templates without a `base.outlet` node. According to the logic in [`src/core/templates/templateCompose.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/templateCompose.ts), these templates are filtered out silently rather than throwing an error, as they cannot serve as viable containers for spliced content.

### How does Instatic prevent multiple outlets in the final tree?

The system uses `subtreeHasOutlet` from [`src/core/templates/outlet.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/outlet.ts) to guard against operations that would duplicate a subtree containing an outlet. This ensures the composed tree maintains at most one outlet, which is then removed during the splice operation, leaving no outlet nodes in the published output.

### Can templates nest indefinitely in the chain?

While there is no explicit depth limit in `resolveTemplateChain`, the composition order is strictly outer-to-inner based on the array returned by the matcher. Each template wraps the result of the previous splice, so practical limits depend on the number of matching templates found for the `everywhere` and `postTypes` levels.

### How does the editor know when to show the Content Outlet tile?

The UI logic in [`src/admin/pages/site/module-picker/moduleInserterModel.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/admin/pages/site/module-picker/moduleInserterModel.ts) checks the outlet invariant using helpers like `treeHasOutlet`. The **Content Outlet** tile is disabled for non-template pages and for templates that already contain an outlet, ensuring users cannot create invalid multi-outlet configurations.