When you decide to build in public, transparency is the primary asset. You want your readers, users, and future contributors to see exactly what features are completed, what is in active development, and what is coming next.

But building this dashboard traditionally introduces unnecessary architectural choices: - Spin up a database to host card states? (Adds connection limits and DB maintenance). - Deploy a dynamic API server? (Breaks the $0 idle server cost baseline). - Embed a third-party corporate widget like GitHub Projects? (Leaks reader telemetry and looks generic).

To keep our architecture clean and completely free of operational overhead, we decided to host our roadmap board natively on the blog under /kanban.html.

Here is the exact decoupled design pattern we used to build it.


1. The Zero-Entropy Pipeline

Instead of running an active backend database, we treat our repository as the Single Source of Truth. The tasks are stored inside a plain JSON manifest file (kanban_state.json) directly in the blog’s code structure:

{
  "tasks": [
    {
      "id": "task-1",
      "title": "Refactor Payout splits to Decoupled Settlement",
      "desc": "Splitting 85/15 database writes immediately instead of pooling batch structures.",
      "status": "done"
    }
  ]
}

Every time we write code or complete a task locally, we simply edit this JSON file.


2. Compile-Time HTML Injection

To render this board, we don’t fetch the JSON dynamically over the network when a user opens the page (which would require API calls and trigger cold starts). Instead, the tasks are injected directly into the HTML code at compile-time by our Babashka static site builder.

In bb.edn, we read both the HTML template and the JSON file:

(let [base (slurp "templates/base.html")
      kanban-body (slurp "templates/kanban.html")
      kanban-state (slurp "kanban_state.json")
      rendered (-> base
                   (clojure.string/replace "{{title}}" "lagu-lagu")
                   (clojure.string/replace "{{body | safe }}" kanban-body)
                   (clojure.string/replace "{{kanban-state | safe}}" kanban-state))]
  (spit "public/kanban.html" rendered))

Inside templates/kanban.html, a simple script parses the pre-injected JSON variable and maps the elements to their CSS columns on the fly:

(function() {
  // Directly compiled from local JSON during rendering
  const state = {{kanban-state | safe}};
  
  function renderBoard() {
    state.tasks.forEach(t => {
      const column = document.getElementById('tasks-' + t.status);
      // Create and append task card element...
    });
  }
  renderBoard();
})();

3. The Execution Flow

💻 Developer (Local laptop)
1. Edit kanban_state.json → Git commit & push

🐙 Git Commit & Push
2. Trigger Workflow

☁️ GitHub Actions
bb quickblog render
3. Compile JSON into HTML

🌐 GitHub Pages CDN
4. Serve 100% static file

👥 Blog Reader

  1. Commit: We modify our local kanban_state.json file as we code.
  2. Compile: Pushing to GitHub triggers our Actions runner (bb quickblog render) which compiles the JSON data straight into the static HTML layout.
  3. Serve: The completed static page is pushed to GitHub Pages. The reader loads a 100% pre-compiled HTML page from a globally cached CDN.

The Takeaway

By choosing a decoupled static design, we get: - Zero Server Overhead: Idle costs remain $0. No databases to manage. - Fast Load Times: The browser doesn’t wait for API roundtrips. - Developer Ownership: You write your documentation, posts, and roadmap in plain text files using your favorite local editor. The compiler handles the presentation.

This is the exact setup now running live for lagu-lagu.