McGarrah Technical Blog

AI-Generated HTML Reports on GitHub Pages: A Pattern for Lightweight Dashboards

· 13 min read

Every time I ask an AI coding agent to visualize something — a cost breakdown, a comparison table, an architecture diagram, a status scorecard — it reaches for HTML. Not markdown. Not a chart library. Just a single .html file with inlined CSS and JS that opens in any browser.

These reports are genuinely useful. The problem is they die in the chat window. You save them to your desktop, maybe share one in Slack, and a week later nobody can find it. The URL doesn’t exist because there is no URL.

The kernel of this pattern came from earlier machine learning work. When I published a PyTorch ASR phoneme analysis, the Jupyter notebook’s richest output — inline graphs, model activation heatmaps, audio spectrograms — couldn’t live in a markdown post. So I exported the notebook to a self-contained HTML file and served it directly from the Jekyll site. No processing, no layout wrapping — just an .html file committed to the repo and deployed alongside everything else. It worked immediately. Stable URL, version-tracked in git, viewable by anyone with the link.

That one-off solution sat in my head until AI coding agents started producing the same kind of output: single-file HTML with inlined styles, rich visuals, zero dependencies. The difference is volume — agents produce these constantly. The fix is the same: publish them to the static site. Same Jekyll blog, same GitHub Pages hosting, same GitHub Actions pipeline — but instead of one notebook export, you’re serving a collection of generated reports at stable, bookmarkable URLs.

Why HTML Is the Agent’s Native Visual Language

HTML is the format AI coding agents produce best. When you ask an agent to create a chart, a dashboard, or a one-off analysis visual, it defaults to HTML — and for good reason:

This makes HTML the natural output format for everything from a quick cost comparison table to a full multi-tab operational dashboard. The pattern described here takes that output and gives it a permanent home.

The Pattern

Self-contained HTML reports committed to a Jekyll site, served via GitHub Pages at stable URLs.

An AI agent generates a single .html file from data you provide. You commit it to your Jekyll repository. GitHub Actions builds and deploys. The result is a permanent, bookmarkable URL that you can share, update, and track in git history.

Key Properties

Architecture

flowchart LR
    A["Data Sources"] --> B["AI Agent"]
    B --> C["Jekyll Site Repo"]
    C --> D["GitHub Actions"]
    D --> E["GitHub Pages"]

Jekyll Site Layout

your-site/
├── _posts/              # regular blog posts (markdown)
├── _drafts/             # unpublished drafts
├── reports/             # HTML reports (self-contained)
│   ├── index.html       # landing page listing all reports
│   ├── cost-summary.html
│   ├── infra-scorecard.html
│   └── usage-trends.html
├── _data/               # source data for generators (optional)
│   ├── costs.csv
│   └── metrics.json
└── tools/               # generator scripts (optional)
    └── generate_report.py

The reports/ directory sits alongside your regular Jekyll content. Jekyll passes .html files through without processing (no Liquid, no layout wrapping) as long as they don’t have front matter. Your reports render exactly as the agent produced them.

Report Design Standards

What makes a good AI-generated report

Visualization without libraries

Data Shape Chart Type Implementation
Categories ranked by value Horizontal bar CSS percentage-width divs
Part-of-whole (< 6 segments) Donut/ring CSS conic-gradient
Two values compared per row Grouped bar CSS divs side by side
Time-series trend (compact) Sparkline Inline SVG <polyline>
Architecture diagrams Box-and-arrow Inline SVG or Mermaid (if Jekyll-rendered)
Flow/sequence diagrams Flowchart, sequence Mermaid in markdown posts; inline SVG in HTML reports

Interactivity

All of this works without any external dependencies. The agent knows these patterns well.

A note on Mermaid

For diagrams in markdown blog posts (like this one), Mermaid is the better choice — it renders from text descriptions during the Jekyll build, stays version-controllable, and diffs cleanly. For self-contained HTML reports, inline SVG is preferable since Mermaid requires a JS library. The rule: Mermaid for blog content, inline SVG for standalone reports.

Content Development Workflow

Markdown First, HTML Second

A report has two layers: the content (what it says) and the presentation (how it looks). Iterating on both simultaneously in HTML is expensive — every change touches markup, styling, and content together.

The recommended workflow separates these concerns:

flowchart LR
    subgraph Iterate["Iterate (cheap)"]
        A[Draft in Markdown]
    end
    subgraph Build["Stabilize"]
        B[Finalize structure & data]
    end
    subgraph Publish["Generate (one-shot)"]
        C[Produce HTML report]
    end
    A -->|content locked| B
    B -->|data pipeline ready| C
Concern Markdown iteration Direct HTML iteration
Token cost per cycle Low — plain text, no markup overhead High — CSS, JS, HTML structure on every edit
Reviewability Easy to diff, easy to read Harder to spot content changes in markup noise
Repeatability Content is reusable across formats Content is locked inside one HTML file
Separation of concerns Content decisions separate from styling decisions Everything tangled together

Step 1: Draft content in markdown. Work with the agent to define what the report should contain — sections, narrative text, data requirements, table structures, key metrics. Iterate until the content is right. This is cheap.

Step 2: Finalize the data pipeline. Once the content structure is stable, build the script that fetches/transforms the data.

Step 3: Generate HTML as a build step. The generator reads the data and content structure, then produces the final HTML. This is a one-way transformation — you don’t edit the HTML directly.

When to skip this

For quick one-off visuals (a single table, a quick chart to share), going straight to HTML is fine. The markdown-first workflow pays off when:

Publishing to GitHub Pages

Manual workflow (quick sharing)

# Agent generates the report
# You review it locally (open in browser from file://)
# Commit and push

git add reports/my-report.html
git commit -m "Update cost summary report with May data"
git push

The existing GitHub Actions Jekyll workflow builds and deploys. Your report is live at https://yoursite.org/reports/my-report within minutes.

Automated workflow (living dashboards)

For reports that refresh on a schedule, add a GitHub Actions workflow:

name: Generate Reports
on:
  schedule:
    - cron: '0 10 * * 1'  # Every Monday at 10:00 UTC
  workflow_dispatch:       # Manual trigger

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-python@v5
        with:
          python-version: '3.12'

      - name: Install dependencies
        run: pip install -r tools/requirements.txt

      - name: Generate reports
        run: python tools/generate_report.py
        env:
          DATA_SOURCE_TOKEN: $

      - name: Commit and push
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git add reports/
          git diff --staged --quiet || git commit -m "Auto-update reports $(date +%Y-%m-%d)"
          git push

This generates fresh reports weekly and commits them. The regular Jekyll deploy workflow picks up the change and publishes.

Two URL strategies

Stable URL (living dashboards): Same filename, overwritten on each update. One URL always shows the latest.

https://yoursite.org/reports/cost-summary

Dated snapshot (point-in-time deliverables): Date in the filename. Each publish creates a new file. Old versions stay accessible.

https://yoursite.org/reports/cost-summary-2026-05-26

When to Use HTML Reports vs Markdown Posts

Use Case Format Why
Analysis you want to share quickly HTML report Rich visuals, self-contained, no Jekyll processing
Permanent reference article Markdown post Integrated with site navigation, RSS, SEO
Living dashboard (refreshed regularly) HTML report Generated from data, not hand-written
Tutorial or how-to Markdown post Benefits from site layout, comments, related posts
One-off presentation or slide deck HTML report Custom layout, animations, speaker notes
Quick throwaway comparison HTML report Fast to generate, easy to share URL, easy to delete later

The key insight: markdown posts are for content you write; HTML reports are for content the agent generates from data. They coexist in the same repository and the same deployed site.

Example: A Simple Report Generator

The real power of this pattern shows when you separate content from generation. Here’s a minimal but complete example:

_data/report-content.json — what the report says (iterate here cheaply):

{
  "title": "Monthly Infrastructure Summary",
  "methodology": "Costs use AmortizedCost metric from AWS Cost Explorer.",
  "assumptions": [
    "Monthly projections extrapolate partial-month data to 30 days linearly",
    "Spot instance savings are excluded from baseline comparisons"
  ]
}

tools/generate_report.py — how the report looks (change rarely):

#!/usr/bin/env python3
"""Generate a self-contained HTML report from CSV data and content config."""

import csv
import json
from pathlib import Path
from datetime import datetime

def generate_report(data_path: Path, content_path: Path, output_path: Path):
    """Read CSV data + content config and produce a single-file HTML report."""

    with open(content_path) as f:
        content = json.load(f)

    with open(data_path) as f:
        rows = list(csv.DictReader(f))

    total = sum(float(r['cost']) for r in rows)
    generated = datetime.now().strftime('%Y-%m-%d %H:%M')
    title = content["title"]
    methodology = content.get("methodology", "")

    html = f"""<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{title}</title>
    <style>
        * 
        body 
        h1 
        .meta 
        table 
        th, td 
        th 
        .total 
    </style>
</head>
<body>
    <h1>{title}</h1>
    <p class="meta">Generated: {generated}</p>
    <p class="meta" style="font-size:12px">{methodology}</p>
    <div class="total">$</div>
    <table>
        <thead><tr><th>Service</th><th>Cost</th></tr></thead>
        <tbody>
"""
    for row in sorted(rows, key=lambda r: float(r['cost']), reverse=True):
        html += f"            <tr><td>{row['service']}</td><td>${float(row['cost']):,.2f}</td></tr>\n"

    html += """        </tbody>
    </table>
</body>
</html>"""

    output_path.write_text(html)
    print(f"Report generated: {output_path}")

if __name__ == "__main__":
    generate_report(
        data_path=Path("_data/costs.csv"),
        content_path=Path("_data/report-content.json"),
        output_path=Path("reports/cost-summary.html")
    )

The key insight from working with this pattern: report-content.json is where you iterate with the agent. Changing what the report says (title, methodology notes, assumptions, section headings) is cheap — just editing a JSON file. Changing how it looks (the generator) is expensive. Separating them means you can refine content in 10 iterations for the token cost of one HTML rewrite.

Why Not Just Use a Dashboard Tool?

Tools like Grafana, Metabase, or Looker are great for operational dashboards backed by live data sources. But they require:

AI-generated HTML reports are the opposite end of the spectrum:

They’re not a replacement for Grafana. They’re a replacement for “let me paste this table into Slack” or “I’ll put together a quick slide for the meeting.”

Categories: technical, devtools, jekyll

About the Author: Michael McGarrah is a Cloud Architect with 25+ years in enterprise infrastructure, machine learning, and system administration. He holds an M.S. in Computer Science (AI/ML) from Georgia Tech and a B.S. in Computer Science from NC State University, and is currently pursuing an Executive MBA at UNC Wilmington. LinkedIn · Substack · GitHub · ORCID · Google Scholar · Resume