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

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, 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.
  • Layer C emits <instatic-hole> placeholders for request-dependent fragments that are lazily fetched at runtime, as seen in 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 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. 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:

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 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.
  • 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; 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. 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. 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, which defines the LRU cache logic, peek and getOrRender functions, and single-flight management. It is consumed by server/publish/publicRouter.ts for public request handling and coordinated with server/publish/publishState.ts for version management and locking.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →