# How Templates and Layouts Work in Instatic: The Template Chain Architecture

> Discover how Instatic templates and layouts work with its template chain architecture. Learn how global layouts wrap page templates for unified static pages.

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

---

**In Instatic, templates and layouts work through a hierarchical template chain system where global layouts marked as "everywhere" wrap around specific page templates, and the `composeTemplateChain` function merges them into a unified static page tree at publish time.**

The Instatic static site generator (CoreBunch/Instatic) implements a unique template composition system that separates layout wrappers from content templates. Understanding how templates and layouts work in Instatic requires examining the template chain resolution and node tree composition that happens during the build process.

## Core Concepts of the Instatic Template System

### Template Definitions

In [`src/core/templates/templatePreviewData.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/templatePreviewData.ts), templates are defined as JSON-like declarations that describe pages through slugs, root nodes, and metadata. These definitions serve as the foundation for both **layouts** and **regular page templates**.

### The Everywhere Layout

The global layout system centers on the `everywhere` function found in [`src/core/templates/templateMatching.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/templateMatching.ts). When you call `everywhere('layout')`, you register a template whose `kind` is `"layout"` into a special slot that applies to every route. This layout typically contains a body node with an outlet where inner content is inserted.

### Template Chain Resolution

When a request hits the server, `resolveTemplateChain(site, requestInfo)` (also in [`src/core/templates/templateMatching.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/templateMatching.ts)) walks the site's template configuration to build an ordered array. The chain always starts with the everywhere layout (if configured), followed by any content-type-specific layouts, and ends with the target page or entry template.

### Dynamic Bindings

Scoped data flow is handled in [`src/core/templates/dynamicBindings.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/dynamicBindings.ts). These bindings (like `{{page.title}}`) flow from the layout scope down to inner pages, allowing outer wrappers to reference page-specific metadata while maintaining the hierarchical structure.

## How Layout Composition Works

The merging process occurs in `composeTemplateChain`, which is tested in [`src/core/templates/templateCompose.test.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/templateCompose.test.ts). This function performs three critical steps:

1. **Starts** with the outermost layout's root node
2. **Locates** the layout's outlet node (the insertion point marked as `type: 'slot.outlet'`)
3. **Replaces** the outlet recursively with the next template's tree

If a layout lacks an outlet node, the page body attaches directly under the layout's body node. This allows for "plain" layouts that only provide CSS or headers without content injection.

## Layout vs. Page Templates

**Layout templates** contain `slug: 'layout'` (or custom identifiers) and are registered via `everywhere()` or `forPosts()` in the site configuration. Their node trees include structural elements like headers, footers, and crucially, a `slot.outlet` node where content merges.

**Page templates** are regular page definitions that supply actual content. When composed, their node trees splice into the layout's outlet, creating the final document structure.

## Implementation Example

Define a global layout in [`src/site/layout.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/site/layout.ts):

```typescript
export const layout = (): Page => ({
  id: 'layout',
  slug: 'layout',
  title: 'Site Layout',
  rootNodeId: 'L_body',
  nodes: {
    L_body: {
      type: 'base.container',
      tag: 'div',
      children: [{ type: 'slot.outlet', name: 'default' }],
    },
  },
});

```

Register the layout in [`src/site/site.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/site/site.ts):

```typescript
import { layout } from './layout';
import { everywhere, forPosts } from '@core/templates';

export const site = site([
  everywhere('layout'),
  forPosts('postLayout'),
]);

```

Create a page template in [`src/site/pages/about.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/site/pages/about.ts):

```typescript
export const aboutPage = (): Page => ({
  id: 'about',
  slug: 'about',
  title: 'About Us',
  rootNodeId: 'A_body',
  nodes: {
    A_body: {
      type: 'base.container',
      tag: 'section',
      children: [{ type: 'base.text', text: 'Welcome!' }],
    },
  },
});

```

When a user visits `/about`, the system executes:

```typescript
const chain = resolveTemplateChain(site, { kind: 'page' });
const mergedTree = composeTemplateChain(chain, { kind: 'page', page: aboutPage });

```

The resulting `mergedTree` contains the layout's wrapper with the "About Us" content spliced into the outlet, ready for static HTML generation.

## Summary

- **Template chains** are resolved per request via `resolveTemplateChain` in [`src/core/templates/templateMatching.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/templateMatching.ts), creating an ordered array from global layouts to specific pages
- **Layout composition** occurs through `composeTemplateChain`, which merges node trees by replacing outlet nodes with content
- **Everywhere layouts** are registered using the `everywhere()` function and serve as the root wrapper for all pages
- **Static generation** happens at publish time, producing fully static HTML without runtime React overhead
- **Dynamic bindings** support scoped data flow from layouts to nested content through [`src/core/templates/dynamicBindings.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/dynamicBindings.ts)

## Frequently Asked Questions

### What is the difference between a layout and a template in Instatic?

In Instatic, a layout is a special template marked with `kind: "layout"` that wraps around other content. While regular templates define specific page content, layouts contain outlet nodes where that content is inserted. Layouts are registered globally via `everywhere()` or for specific content types via `forPosts()`, whereas page templates are individual entries in the template chain.

### How does the everywhere layout function work?

The `everywhere` function in [`src/core/templates/templateMatching.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/templateMatching.ts) places a layout into the global template slot, ensuring it applies to every route. When `resolveTemplateChain` runs, it always includes the everywhere layout as the first element in the returned array, making it the outermost wrapper in the final composition.

### Where does template composition happen in the codebase?

Template composition is implemented in `composeTemplateChain`, which is tested in [`src/core/templates/templateCompose.test.ts`](https://github.com/CoreBunch/Instatic/blob/main/src/core/templates/templateCompose.test.ts). This function takes the resolved template chain and merges the node trees by finding outlet nodes and splicing in the next template's content recursively until a single unified tree remains.

### Are Instatic layouts rendered at runtime or build time?

Instatic layouts are composed entirely at build time. The `composeTemplateChain` function merges layout and page trees during the publishing process, resulting in static HTML that requires no runtime JavaScript for layout rendering. This architecture eliminates client-side hydration overhead for layout structures.