# How Instatic's Three‑Layer Publishing System Delivers Sub‑Millisecond Response Times

> Discover how Instatic's three-layer publishing system achieves sub-millisecond response times by serving pre-baked HTML from disk, using memory cache, and hydrating dynamic fragments on demand.

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

---

**Instatic eliminates per‑request rendering costs by serving pre‑baked HTML from disk, falling back to an in‑memory LRU cache, and hydrating only dynamic fragments on demand, achieving response times as low as 0.6 ms for static content.**

The **three‑layer publishing system** in the CoreBunch/Instatic repository bridges the gap between static site speed and dynamic application flexibility. By combining atomic disk writes, intelligent in‑memory caching, and fine‑grained dynamic islands, the system ensures that fully static pages bypass the database entirely while still supporting personalized content where required.

## Layer A: Disk‑Baked Artefacts for Zero‑Cost Serving

When a site is published, `publishPage` writes complete HTML files to `uploads/published/current/<route>.html` via [`staticArtefact.ts`](https://github.com/CoreBunch/Instatic/blob/main/staticArtefact.ts)【/server/publish/staticArtefact.ts#L1-L9】. The write operation uses an atomic two‑slot symlink swap, ensuring readers never encounter a half‑written file.

The HTTP handler in [`publicRouter.ts`](https://github.com/CoreBunch/Instatic/blob/main/publicRouter.ts) checks this directory first. If the request URL contains only "junk" query parameters, the file streams directly from disk in approximately **0.6–1.4 ms** with **no database lookup, no template rendering, and no plugin execution**【/server/publish/publicRouter.ts#L64-L68】. This represents the fastest path through the three‑layer publishing system.

### Atomic Publishing Mechanics

The atomic swap mechanism guarantees consistency during deployments. While the new artefact writes to a staging slot, existing traffic continues reading from the current slot. Once the write completes, the symlink flips instantly, making the new version live without dropping connections or serving corrupted HTML.

## Layer B: In‑Memory LRU Cache for Dynamic Routes

For routes containing request‑dependent data (loops, bindings, or pagination), [`renderCache.ts`](https://github.com/CoreBunch/Instatic/blob/main/renderCache.ts) stores rendered HTML in an LRU cache keyed by `(urlPath, canonicalQuery)`【/server/publish/renderCache.ts#L1-L3】. The `canonicalRenderQuery` utility normalizes the key by stripping non‑essential parameters while preserving loop pagination values【/server/publish/loopPrefetch.ts#L60-L62】.

A cache hit returns the pre‑rendered body in approximately **0.8 ms** without touching the database or re‑walking the page tree. When content changes, `bumpPublishVersion()` in [`publishState.ts`](https://github.com/CoreBunch/Instatic/blob/main/publishState.ts) evicts the entire cache, guaranteeing freshness and preventing stale data from persisting across publishes【/server/publish/publishState.ts#L49-L51】.

## Layer C: Dynamic‑Island "Hole" Runtime for Lazy Hydration

Nodes that require request‑specific data are identified by `findDynamicNodeIds` in [`dynamicDetection.ts`](https://github.com/CoreBunch/Instatic/blob/main/dynamicDetection.ts)【/src/core/publisher/dynamicDetection.ts#L74-L86】. Instead of baking this content into the HTML, the publisher emits an `<instatic‑hole>` placeholder containing a minimal IntersectionObserver script (approximately **1.1 KB**)【/server/publish/holeRuntime.ts#L50-L53】.

When the browser scrolls the hole into view, the client fetches the fragment from `/_instatic/hole/<nodeId>`, triggering a targeted server render for that subtree only. This keeps initial page loads fully static while deferring computation for truly dynamic content until it is actually needed.

## Implementing the Three‑Layer Pipeline

The following examples demonstrate how these layers interact in practice.

Trigger the atomic bake process that populates Layer A:

```typescript
import { publishDraftSite } from 'server/publish/publishSite';

// Writes static artefacts to disk and bumps publish version
await publishDraftSite(db, siteId);

```

The request handler implements the Layer A → Layer B → fallback cascade:

```typescript
// Inside server/publish/publicRouter.ts

// Layer A: Attempt disk read first
const artefact = readArtefast(uploadsDir, url.pathname);
if (artefact) {
  return streamFile(artefact); // ~0.6-1.4ms response
}

// Layer B: Check LRU cache for canonical query combinations
const cacheKey = canonicalRenderQuery(url);
const cached = renderCache.getOrRender(cacheKey, async () => {
  const { html } = await publishPage(page, site, registry);
  return { body: html };
});
if (cached) {
  return sendResponse(cached.body); // ~0.8ms response
}

```

Mark components as dynamic to enable Layer C hole injection:

```tsx
<div className="container">
  <h1>Static Header</h1>
  {/* Becomes <instatic-hole id="node-123"> at publish time */}
  <DynamicWidget userId={user.id} />
</div>

```

When `DynamicWidget` exports `dynamic: true` from its module definition, `publishPage` in [`render.ts`](https://github.com/CoreBunch/Instatic/blob/main/render.ts) replaces its output with a hole placeholder. The [`holeRuntime.ts`](https://github.com/CoreBunch/Instatic/blob/main/holeRuntime.ts) client script then manages lazy hydration as the user scrolls.

## Summary

- **Layer A (Disk Artefacts)** delivers fully static pages in under 1.5 ms by streaming pre‑baked HTML directly from the filesystem, bypassing all application logic.
- **Layer B (LRU Cache)** serves semi‑dynamic pages in under 1 ms by storing rendered HTML in memory and invalidating atomically via version bumps.
- **Layer C (Dynamic Islands)** preserves static‑site performance for initial loads while supporting real‑time data through lazy‑loaded fragments fetched only when visible.
- **Atomic Publishing** ensures consistency during deployments through symlink swapping, preventing readers from seeing intermediate states.

Together, these layers give Instatic near‑static‑site speed while maintaining support for dynamic content where required.

## Frequently Asked Questions

### How does Instatic handle cache invalidation when content changes?

The `bumpPublishVersion()` function in [`server/publish/publishState.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/publish/publishState.ts) increments a global version counter whenever a publish completes【/server/publish/publishState.ts#L49-L51】. This version is captured at the start of each render, and any render that completes after a version change is discarded. Simultaneously, the LRU cache in [`renderCache.ts`](https://github.com/CoreBunch/Instatic/blob/main/renderCache.ts) is cleared, ensuring subsequent requests receive freshly baked HTML.

### What happens if a dynamic island is never scrolled into view?

The `<instatic‑hole>` element includes a lightweight IntersectionObserver that only triggers the fetch to `/_instatic/hole/<nodeId>` when the element enters the viewport【/server/publish/holeRuntime.ts#L50-L53】. If the user never scrolls to that section, the server never renders the fragment, saving both server CPU and network bandwidth.

### How does the three‑layer system differentiate between query parameters?

Layer A serves requests with "junk" query parameters (UTM tags, tracking IDs) directly from disk, treating them as cacheable static requests. Layer B uses `canonicalRenderQuery` to normalize the cache key, stripping irrelevant parameters while preserving loop pagination values needed for dynamic content【/server/publish/loopPrefetch.ts#L60-L62】. This ensures pagination works correctly while maximizing cache hits.

### Can personalized content be served from Layer A?

No. Layer A artefacts are completely static HTML files written during the publish process. Personalized content must flow through Layer C as dynamic islands identified by `findDynamicNodeIds`【/src/core/publisher/dynamicDetection.ts#L74-L86】. The publisher inserts hole placeholders for these nodes, and the personalization logic executes only when the browser requests the specific fragment endpoint.