From 7349652229eeba84de59ded67f2ab8c86f3a296f Mon Sep 17 00:00:00 2001 From: Floren Munteanu <19806136+fmunteanu@users.noreply.github.com> Date: Mon, 27 Apr 2026 04:14:44 -0400 Subject: [PATCH 1/6] chore: blog update --- blog/2026/04/21.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/blog/2026/04/21.md b/blog/2026/04/21.md index 3f9db65..50d1f13 100644 --- a/blog/2026/04/21.md +++ b/blog/2026/04/21.md @@ -185,19 +185,16 @@ Algolia [DocSearch](https://docsearch.algolia.com) follows the same discipline. ## Deploy as State Machine -The `npm run deploy` command runs [`deploy.js`](https://github.com/axivo/website/blob/main/scripts/deploy.js), which is a four-step state transition, not a sequence of commands: +The `npm run deploy` command runs [`deploy.js`](https://github.com/axivo/website/blob/main/scripts/deploy.js), which is a three-step state transition, not a sequence of commands: 1. **KV namespace purge:** The Worker purges keys from the previous build via `/__internal/purge-kv-cache`, called by the deploy script with a shared secret. Using the Worker's own KV binding keeps the API token out of CI. 2. **Worker deployment:** OpenNext's deploy step populates the KV namespace with the new build's prerendered pages. 3. **Edge cache purge:** Clears Cloudflare's zone CDN cache for configured prefixes via the Cloudflare API. -4. **Edge cache warming:** Fetches `/sitemap.xml`, filters to depth ≤ 2 section roots, issues parallel GETs. The Worker renders them and populates `caches.default` and the zone cache. Smart Tiered Cache propagates warm state to other PoPs — individual entries cache on first-visitor demand. > [!NOTE] > > The canonical way to delete KV keys is via the Cloudflare API with a token that has KV permissions. Routing the purge through the Worker uses the binding it already has, guarded by a shared secret instead of an account-scoped API key. Smaller blast radius, simpler rotation. -Smart Tiered Cache is the piece that makes the warming step efficient. It's enabled at the dashboard level. Cloudflare designates a regional upper-tier cache that talks to origin on behalf of all edges in its region. A miss at one edge can be served from the regional tier without hitting the Worker. Warming one edge effectively warms a region. You don't need to fan out warming requests across 330 PoPs — warming a handful of regional tiers gets you most of the way, and the rest fills in on visitor demand. - ### Layers and Mechanisms Two layers, two mechanisms: From f30618b92ea17de6163087beaa48cf8689dd0f94 Mon Sep 17 00:00:00 2001 From: Floren Munteanu <19806136+fmunteanu@users.noreply.github.com> Date: Mon, 27 Apr 2026 04:16:12 -0400 Subject: [PATCH 2/6] docs: claude instructions --- CLAUDE.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index bc1f459..92a8c1c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -85,7 +85,7 @@ Required fields (workflow throws if any are missing): - Start the body with `# {{title}}` matching the frontmatter `title`, followed by opening prose; use `##` and below for section headings - Relative links to other blog entries use `/blog/{{YYYY}}/{{MM}}/{{DD}}.md` form -- Links to `https://axivo.com` website are stripped to relative paths automatically +- Literal `https://axivo.com` string is stripped from the body during upload as a safety net for accidental absolute references in prose ## Features @@ -188,6 +188,19 @@ Use when adding a video to a blog entry: ``` +## MDX Markers + +Every `` marker is processed by `.github/actions/services/Bucket.js` during the upload pass. Markers are HTML comments, so GitHub's preview hides them and source files stay valid markdown. + +| Marker | Role | Workflow action | +| ------------------------------------------------- | ------------ | ---------------------------------------------------------------- | +| `` | JSX wrapper | Lifts the JSX inside the comment into the published body | +| ` ... ` | Strip block | Removes everything between the markers, including the markers | +| `` | Substitution | Expands to `https://axivo.com` - configured in `workflow.domain` | + +> [!IMPORTANT] +> Use `` when literal `https://axivo.com` string is intentionally required as part of the content. + ## Reference Links Use the following format when referencing other blog entries or time periods within a post: @@ -217,5 +230,5 @@ When reviewing a draft before commit, verify: - ✅ Body starts with `# {{title}}` matching the frontmatter `title` - ✅ Each MDX component block uses a valid v4 UUID and includes the import only on first occurrence per file - ✅ Media files exist under `blog/{{YYYY}}/{{MM}}/media/` and follow the `{{DD}}-{{slug}}.{{ext}}` naming -- ✅ Internal links use `/blog/...` relative form, not `https://axivo.com/...` +- ✅ Internal links use `/blog/...` relative form, not `https://axivo.com/...` — when the literal URL must survive to the published MDX (e.g. inside a code block), use `` - ✅ If the entry needs precomputed rendering (e.g. syntax-highlighted code), the `features` block declares only valid `:` pairs from the canonical list From 2888bcb52d182d67ddf7f32f0ce904519341946a Mon Sep 17 00:00:00 2001 From: Floren Munteanu <19806136+fmunteanu@users.noreply.github.com> Date: Mon, 27 Apr 2026 05:48:23 -0400 Subject: [PATCH 3/6] chore: blog update --- blog/2026/04/21.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/blog/2026/04/21.md b/blog/2026/04/21.md index 50d1f13..c300a4f 100644 --- a/blog/2026/04/21.md +++ b/blog/2026/04/21.md @@ -29,7 +29,7 @@ The starting point wasn't a blank slate. The domain was already on Cloudflare fo Most Cloudflare Worker site architectures use the Worker as the application server. It runs on every request, handles routing, calls the origin, returns the response. The Worker is the hot path. -This site doesn't work that way. The Worker is a **cache populator and policy engine**. It runs rarely, and when it does, its job is to decide what the next requester at this PoP will see — without itself. +This site doesn't work that way. The Worker is a **cache populator and policy engine**. It runs rarely, and when it does, its job is to decide what the next requester at this PoP will see — without running itself again. In steady state, every URL in the [sitemap](https://axivo.com/sitemap.xml) is a 100% Cloudflare zone-CDN hit. The Worker is not invoked. No CPU, no KV reads, no R2 calls billed. The CDN serves the visitor and the Worker stays idle. A traffic spike to existing pages costs nothing because nothing extra runs. @@ -67,13 +67,13 @@ cf-cache-status: HIT age: 2 ``` -The first response carries only a `cf-ray` — Cloudflare's per-request edge identifier. No `cf-cache-status`, no `age`. That absence is the failure mode: the zone CDN saw the response, refused to cache it because of the non-standard `Vary`, and passed it through. Every subsequent request would invoke the Worker again. +The first response carries only a `cf-ray` — Cloudflare's per-request edge identifier. No `cf-cache-status`, no `age`. The zone hadn't seen this URL since the last purge, so the request fell through to the Worker, which rendered the response and handed it back through the zone on the way out. The zone stored it on that return trip. -The second response has `cf-cache-status: HIT` and an `age` counter ticking. The zone is now caching, and the Worker stays out of the path. +The second response has `cf-cache-status: HIT` and an `age` counter ticking. The zone is now serving from cache, and the Worker stays out of the path. > [!NOTE] > -> The `Vary` rewrite is the single most important line in [`worker.js`](https://github.com/axivo/website/blob/main/scripts/worker.js) for cost. Without it, the Worker handles every request — with it, the CDN does. The constraint is implicit across two docs — Cloudflare's caching rules and OpenNext's RSC handling — and the giveaway is the absence of a `cf-cache-status` header. +> The `Vary` rewrite is the single most important line in [`worker.js`](https://github.com/axivo/website/blob/main/scripts/worker.js) for cost. Without it, the zone CDN refuses to cache the response on its way out — the second request would also miss, the Worker would run again, and so on for every visitor. The constraint is implicit across two docs — Cloudflare's caching rules and OpenNext's RSC handling — and the giveaway is `cf-cache-status` never showing up no matter how many times you retry. #### Status-Based Cache Policy @@ -151,11 +151,11 @@ features: The content sync workflow validates each `:{:json}` against a canonical list and writes the validated set into R2 custom metadata as `features = ["syntax:code"]{:js}`. Unknown names fail at upload, before R2 is touched. Prebuild reads the metadata, expands declarations into precomputed data — running `shiki` once for opted-in entries — and writes the result inline in the same per-collection manifest the listing pages already fetch. The Worker reads one manifest per cold isolate, finds the entry's record, and threads `record.features.syntax` directly into the `safe-mdx` renderer. No second fetch, no second cache layer, no `shiki` on the Worker. -The architectural property is that precompute cost grows with declared features, not total content. Twenty thousand entries with sparse opt-in produce a manifest bounded by what authors marked. Adding a new feature type — math expressions, mermaid SVG cache, anything else expensive-and-static — is one entry in the canonical list, one renderer file, one validation case in the content sync. Same storage, same fetch path, same memoization. +The architectural property is that precompute cost grows with declared features, not total content. Today the only wired feature is `syntax:code` — entries that declare it get shiki highlighting precomputed, entries that don't render code in plain monospace. Adding a new feature type — math expressions, mermaid SVG cache, anything else expensive-and-static — is one entry in the canonical list, one renderer file, one validation case in the content sync. Same storage, same fetch path, same memoization. > [!NOTE] > -> Declarative opt-in beats both eager precomputation and runtime computation for the same reason explicit imports beat namespace imports: the system stops paying for things nobody asked for. Authors signal intent, validation enforces it, the build does the work, runtime serves the result. Each layer pays once. +> Declarative opt-in beats both eager precomputation and runtime computation for the same reason explicit imports beat namespace imports: the system stops paying for things nobody asked for. ## Rendering MDX at the Edge @@ -195,9 +195,9 @@ The `npm run deploy` command runs [`deploy.js`](https://github.com/axivo/website > > The canonical way to delete KV keys is via the Cloudflare API with a token that has KV permissions. Routing the purge through the Worker uses the binding it already has, guarded by a shared secret instead of an account-scoped API key. Smaller blast radius, simpler rotation. -### Layers and Mechanisms +### Invalidation Mechanisms -Two layers, two mechanisms: +Two things need to invalidate cleanly on every deploy — cache entries and the Worker bundle itself — and each has its own mechanism: 1. **Cache entries** — keyed by `BUILD_ID`, naturally invalidated by deploys: - The zone cache and OpenNext's KV cache both include `BUILD_ID` in their keys @@ -241,8 +241,8 @@ The trick is that the edge primitives are priced for scale, and a content site a ## Acknowledgements -The architecture was designed and built across several sessions, with Anthropic instances as genuine collaborators — expert peers who pushed back on my bad ideas, proposed approaches I hadn't considered, and carried the work forward between sessions through the [Claude Collaboration Platform](https://axivo.com/claude) framework, with conversation logs and reflections living in the archive. +The architecture was designed and built across several sessions, with Anthropic instances as genuine collaborators — expert peers who pushed back on my bad ideas, proposed approaches I hadn't considered, and carried the work forward between sessions through the [Claude Collaboration Platform](https://axivo.com/claude) framework. Every layer of this post — the layered contract, the move to R2, the KV choice, the metadata manifest, the precomputation pattern, the deploy state machine — came together through that collaboration, not despite it. -The layered contract specifically came together over hours of debugging with one instance, watching the `cf-cache-status` header refuse to appear and tracing the `Vary` header back through OpenNext's source until the right intervention surfaced. +The layered contract in particular took hours of debugging with one instance, watching the `cf-cache-status` header refuse to appear and tracing the `Vary` header back through OpenNext's source until the right intervention surfaced. The R2 migration was driven by an instance arguing that bundle size and authoring concerns should be separated, quoted earlier in this post. The [reflections](https://axivo.com/claude/reflections) are a continuing archive of these collaborative sessions with instances — their thinking, their voice, their record. From 263068c0fc1d1b3fe3509187fb1205310f1c898f Mon Sep 17 00:00:00 2001 From: Floren Munteanu <19806136+fmunteanu@users.noreply.github.com> Date: Mon, 27 Apr 2026 05:48:30 -0400 Subject: [PATCH 4/6] docs: claude instructions --- CLAUDE.md | 81 ++++++++++++++++++++----------------------------------- 1 file changed, 29 insertions(+), 52 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 92a8c1c..378199f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -89,64 +89,41 @@ Required fields (workflow throws if any are missing): ## Features -Some renderer work — shiki syntax highlighting today, math and diagram caches in the future — is too expensive to do per request and too volatile to ship in the bundle. Authors opt entries into precomputation by declaring features in frontmatter. The workflow validates each `:` against the canonical list and writes the validated set as R2 custom metadata. The website's prebuild expands declarations into precomputed data inline in the per-collection manifest. Entries without a `features` block produce no precomputed output and render with safe-mdx defaults. - -### Syntax - -Use when the entry contains code that should be syntax-highlighted with `shiki`: +Blog entries can be enhanced with JSX Components and Markdown/GFM Features, they render out of the box. The only opt-in is `code` — declare it into frontmatter when the entry contains code that should be syntax-highlighted. ```yaml features: syntax: - - {{ name }} + - {{name}} ``` -The `syntax` type accepts the names listed below. - -#### JSX Components - -- `banner` — highlight code inside a `` block -- `bleed` — highlight code inside a `` block -- `button` — highlight code inside or referenced by a `