Architecture

How content is served at request time, pinned to a commit, cached, and revalidated on push.

Most docs frameworks parse Markdown at build time and bundle the result. comark-docs does it at request time instead: pages are fetched from GitHub, parsed on demand, and cached aggressively. This page explains the moving parts.

Serving modes

Every page exists in three modes, selected by the URL:

ModeURLContent
prod/getting-started/introductionpinned production SHA
tree/tree/main/getting-started/introductionlatest content commit on the branch
blob/blob/<sha>/getting-started/introductionimmutable commit preview

Production and previews use the same rendering pipeline — see Versioned previews for the tree and blob modes.

In development, none of this applies: content is read straight from your working tree with hot reload.

Pinned to a commit

Production doesn't read "whatever is on main right now." On each request, the server resolves the latest commit touching the content directory on the production branch (a shared, 60-second-TTL cache keeps this to about one GitHub call per minute) and pins every read to that immutable SHA.

Pinning buys two things:

  • Consistency — a request never mixes files from two commits, even mid-push.
  • Cacheability — content at a SHA can never change, so parsed pages are cached hard.

Code-only commits don't move the content SHA, so they don't invalidate anything.

Cache tiers

  1. ISR — rendered HTML is cached at the edge. Pages expire after comarkDocs.isr seconds (default 300) or when a push purges them.
  2. Runtime cache — parsed Markdown bodies and the content manifest, keyed by parser version and content SHA. Because SHAs are immutable, these entries survive deployments and are shared across preview modes.
  3. GitHub — the source of truth. Only hit on cold caches, and always at a pinned SHA.

A cold page costs one GitHub lookup for the SHA, one manifest read, and one page parse. A warm page costs nothing — it's served from the edge.

Revalidation on push

When you push to the production branch, a GitHub webhook calls /api/revalidate:

The handler verifies the webhook signature with WEBHOOK_SECRET, resolves the new content SHA, diffs the file manifests to find affected pages, and purges exactly those from the ISR cache. The next request renders from the new commit — typically live within seconds of the push.

Without the webhook, the site still updates: ISR entries expire on their own after the isr window. The webhook just makes it immediate.

Markdown for agents

Every production documentation page is mirrored as raw Markdown at /raw/<path>.md (versioned previews serve HTML only). The mirrors carry the same ISR caching as the HTML pages.

On Vercel, agents don't need to know the mirror URLs. The layer injects rewrites into the build output, ahead of the ISR cache:

  • A request for any page URL with Accept: text/markdown (or a curl user-agent) is rewritten to its /raw/** mirror.
  • A request for / is rewritten to /llms.txt.

Because the rewrite happens at the routing layer, the HTML and Markdown variants are cached under different paths and can't poison each other's cache entries.

A request for a page that doesn't exist returns a real HTTP 404 with a short Markdown body pointing at /llms.txt, /llms-full.txt, and the sitemap, so an agent that guesses a URL wrong can recover.

No redeploys for content

Since content never ships in the build, a content-only push doesn't need a deployment at all. On Vercel, an Ignored Build Step cancels builds for pushes that only touch content/ — the webhook handles those. Code pushes build and deploy as usual.