Architecture
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:
| Mode | URL | Content |
|---|---|---|
| prod | /getting-started/introduction | pinned production SHA |
| tree | /tree/main/getting-started/introduction | latest content commit on the branch |
| blob | /blob/<sha>/getting-started/introduction | immutable 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
- ISR — rendered HTML is cached at the edge. Pages expire after
comarkDocs.isrseconds (default 300) or when a push purges them. - 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.
- 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.
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.