# What Is the Purpose of the In-Memory Cache in Instatic's Publishing Pipeline?

> Instatic's in-memory cache boosts performance by storing rendered pages, avoiding recomputation and ensuring fresh content with automatic invalidation and deduplication.

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

---

**The in-memory cache in Instatic's publishing pipeline serves as a high-performance LRU render cache that eliminates expensive recomputation of dynamic pages by storing rendered HTML in Layer B, while ensuring stale content is never served through automatic version-tagged invalidation and single-flight deduplication.**

Instatic, developed by CoreBunch, implements a sophisticated three-tier publishing architecture where Layer B maintains an intelligent in-memory cache to accelerate dynamic route rendering. This cache system, located in [`server/publish/renderCache.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/publish/renderCache.ts), acts as a critical performance optimization that balances speed with content freshness. By understanding how this component integrates with the broader publishing pipeline, developers can optimize deployments for high-traffic scenarios while guaranteeing that newly published content is immediately available to visitors.

## Three-Layer Publishing Architecture

Instatic organizes content delivery into three distinct layers that work together to serve static and dynamic content efficiently:

- **Layer A** bakes a full static snapshot of the site to disk for immutable assets.
- **Layer B** holds the **in-memory LRU render cache** that stores rendered HTML for dynamic routes, implemented in [`server/publish/renderCache.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/publish/renderCache.ts).
- **Layer C** emits `<instatic-hole>` placeholders for request-dependent fragments that are lazily fetched at runtime, as seen in [`server/handlers/cms/hole.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/handlers/cms/hole.ts).

The in-memory cache resides specifically in Layer B and acts as a bridge between the static snapshot and dynamic rendering requirements.

## Core Responsibilities of the In-Memory Cache

The cache implementation handles four critical responsibilities to maintain performance without sacrificing correctness.

### Fast Request Path with Peek Operations

When a request arrives, the router in [`server/publish/publicRouter.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/publish/publicRouter.ts) first calls the `peek` function to check if a cached entry exists for the `(urlPath, queryString)` pair. If present, the cached HTML returns immediately, bypassing full-site snapshot parsing and database lookups. This fast path eliminates redundant CPU work for frequently accessed dynamic routes.

### Staleness Protection via Publish Versions

Each cache entry is tagged with the **publish version** managed by [`server/publish/publishState.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/publish/publishState.ts). When a publish occurs, the version bumps monotonically, causing all entries created under previous versions to be treated as cache misses. This mechanism guarantees that newly published changes are never served from stale cache entries, ensuring content consistency across the site.

### LRU Eviction Bounds

The cache size is bounded to prevent unbounded memory growth, defaulting to **1000 entries** and configurable via the `RENDER_CACHE_MAX_ENTRIES` environment variable. Entries are stored in a `Map` where insertion order reflects recency; when the limit is reached, the least-recently-used entry is evicted automatically.

### Single-Flight Deduplication

Concurrent requests for the same cache key share a single factory promise through the `inFlight` map. The underlying render function executes only once, and all callers receive the same result. Errors propagate to all waiters, and critically, `null` results are never cached to prevent poisoning the cache with failed renders.

## Implementation in renderCache.ts

The cache exposes two primary functions for interacting with stored renders:

```typescript
import { peek, getOrRender, RenderCacheKey } from '@/server/publish/renderCache'

// Fast-path: try to read from the cache without invoking the renderer
export async function renderFast(key: RenderCacheKey) {
  const cached = peek(key)
  if (cached) return cached.body   // cache hit
  // cache miss – fall back to full render
  return (await renderAndCache(key)).body
}

// Full render with caching and single-flight deduplication
export async function renderAndCache(key: RenderCacheKey) {
  return getOrRender(key, async () => {
    // Expensive render logic – e.g. compose page tree, run plugins, etc.
    const html = await renderPageTree(key.urlPath, key.queryString)
    return html ? { body: html, headers: {}, status: 200 } : null
  })
}

```

The `getOrRender` function encapsulates the single-flight logic and version checking, while `peek` provides synchronous-style cache inspection for the fast path.

## Integration with Publish State Management

The cache coordinates with [`server/publish/publishState.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/publish/publishState.ts) to maintain consistency during publish cycles. The `withPublishLock` serializer prevents overlapping publish cycles from corrupting the cache state, while the monotonically increasing `publishVersion` provides the versioning mechanism that invalidates stale entries. This integration ensures that cache invalidation happens atomically with content updates, eliminating race conditions between renders and publishes.

## Summary

- The in-memory cache in Instatic's publishing pipeline lives in **Layer B** and is implemented in [`server/publish/renderCache.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/publish/renderCache.ts).
- It provides a **fast request path** via the `peek` function that serves cached HTML for `(urlPath, queryString)` pairs without recomputation.
- **Staleness protection** is achieved by tagging entries with publish versions from [`publishState.ts`](https://github.com/CoreBunch/Instatic/blob/main/publishState.ts); version bumps automatically invalidate old entries.
- The cache uses an **LRU eviction strategy** with a default limit of 1000 entries, configurable via `RENDER_CACHE_MAX_ENTRIES`.
- **Single-flight deduplication** prevents duplicate renders for concurrent requests using the `inFlight` map, with `null` results excluded from caching.
- Integration with `withPublishLock` ensures cache consistency during concurrent publish operations.

## Frequently Asked Questions

### What is the default size limit for Instatic's render cache?

The default limit is **1000 entries**, which can be adjusted by setting the `RENDER_CACHE_MAX_ENTRIES` environment variable. This bound prevents unbounded memory growth while maintaining high hit rates for popular dynamic routes.

### How does Instatic prevent stale content from being served?

The cache implements **version-tagged invalidation** where each entry is associated with the current `publishVersion` from [`server/publish/publishState.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/publish/publishState.ts). When a new publish occurs, the version increments, causing all existing cached entries to be treated as misses on subsequent lookups, ensuring only fresh content is served.

### What happens when multiple requests hit the same uncached route simultaneously?

Instatic uses **single-flight deduplication** via the `inFlight` map in [`renderCache.ts`](https://github.com/CoreBunch/Instatic/blob/main/renderCache.ts). The first request triggers the render, while subsequent concurrent requests wait for the same promise. All callers receive the identical result, and the render executes only once, preventing thundering herd problems on cache misses.

### Where is the in-memory cache located in the Instatic codebase?

The primary implementation resides in **[`server/publish/renderCache.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/publish/renderCache.ts)**, which defines the LRU cache logic, `peek` and `getOrRender` functions, and single-flight management. It is consumed by **[`server/publish/publicRouter.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/publish/publicRouter.ts)** for public request handling and coordinated with **[`server/publish/publishState.ts`](https://github.com/CoreBunch/Instatic/blob/main/server/publish/publishState.ts)** for version management and locking.