tutorial

Monitoring Your Htmx App with Vigilmon: Uptime, Endpoint Health & Alerts

Add production-grade monitoring to Htmx-powered apps — health check endpoints, uptime monitors for your HTML-over-the-wire server, cron heartbeats, and alert routing.

Htmx apps are deceptively simple on the surface — just HTML attributes — but they depend entirely on a reliable server. Every hx-get, hx-post, and hx-trigger fires a real HTTP request, and if your backend is slow or down, your users see partial page updates that silently fail. Vigilmon monitors your Htmx server continuously so you know about outages before users notice frozen swap targets.

What You'll Build

  • A /health endpoint your server exposes for Vigilmon to probe
  • Vigilmon HTTP monitors for your Htmx server and its key AJAX endpoints
  • A cron heartbeat for any background tasks (email digests, cache warmers)
  • Email and Slack alert channels

Prerequisites

  • A backend serving Htmx responses (Express, Flask, Django, Rails, Go — any)
  • A free Vigilmon account

Step 1: Add a Health Endpoint to Your Server

Vigilmon needs a stable URL to probe. Add a /health route that checks your dependencies and returns a JSON status.

Express / Node.js

// server.js
app.get("/health", async (req, res) => {
  const checks = {};
  let degraded = false;

  try {
    await db.query("SELECT 1");
    checks.database = "ok";
  } catch (err) {
    checks.database = `error: ${err.message}`;
    degraded = true;
  }

  res.status(degraded ? 503 : 200).json({
    status: degraded ? "degraded" : "ok",
    checks,
    timestamp: new Date().toISOString(),
  });
});

Flask / Python

# app.py
from flask import jsonify
import time

@app.route("/health")
def health():
    checks = {}
    degraded = False

    try:
        db.session.execute("SELECT 1")
        checks["database"] = "ok"
    except Exception as e:
        checks["database"] = f"error: {e}"
        degraded = True

    status_code = 503 if degraded else 200
    return jsonify({
        "status": "degraded" if degraded else "ok",
        "checks": checks,
        "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
    }), status_code

Go (net/http)

// main.go
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
    status := map[string]interface{}{
        "status":    "ok",
        "timestamp": time.Now().UTC().Format(time.RFC3339),
    }
    w.Header().Set("Content-Type", "application/json")
    json.NewEncoder(w).Encode(status)
})

Step 2: Add Vigilmon HTTP Monitors

Monitor 1: Main Server Uptime

| Field | Value | |---|---| | URL | https://yourdomain.com/health | | Method | GET | | Expected status | 200 | | Expected body | "status":"ok" | | Check interval | 1 minute |

This catches crashes, deploys that fail to start, and database connectivity issues.

Monitor 2: Key Htmx Endpoint

Pick the most critical partial-update route your users depend on and monitor it directly.

| Field | Value | |---|---| | URL | https://yourdomain.com/partials/dashboard | | Method | GET | | Expected status | 200 | | Expected body | Any stable string in the response HTML | | Check interval | 2 minutes |

Create both monitors in Vigilmon under Monitors → New HTTP Monitor.


Step 3: Monitor Out-of-Band Triggers

Htmx supports hx-trigger="every 30s" polling and SSE (hx-ext="sse"). If your SSE stream or polling endpoint goes down, swap targets silently stop updating. Add a dedicated monitor for these routes.

<!-- htmx template -->
<div hx-get="/feed/latest" hx-trigger="every 30s" hx-swap="innerHTML">
  Loading…
</div>

Add a monitor for https://yourdomain.com/feed/latest with an expected 200 response — now you know the instant it stops responding.


Step 4: Heartbeat for Background Jobs

If your Htmx app runs background tasks (sending emails, generating reports, syncing data), set up a Vigilmon heartbeat monitor so you're alerted when a job stops running.

// Node.js cron example
import cron from "node-cron";

const HEARTBEAT_URL = process.env.VIGILMON_HEARTBEAT_URL ?? "";

async function pingHeartbeat() {
  if (!HEARTBEAT_URL) return;
  try {
    await fetch(HEARTBEAT_URL, { signal: AbortSignal.timeout(5000) });
  } catch {
    // monitoring failure must not crash the job
  }
}

cron.schedule("0 * * * *", async () => {
  try {
    await runHourlyJob();
    await pingHeartbeat(); // only ping on success
  } catch (err) {
    console.error("Hourly job failed:", err);
  }
});

In Vigilmon:

  1. Monitors → New Heartbeat Monitor
  2. Set expected interval to 90 minutes
  3. Copy the ping URL into your .env:
VIGILMON_HEARTBEAT_URL=https://vigilmon.online/api/heartbeat/xxxxxxxx

Step 5: Configure Alert Channels

In Vigilmon's Alerts settings:

Email

  1. Go to Alert Channels → Email
  2. Add your on-call email address
  3. Attach to both HTTP monitors

Slack Webhook

  1. Create a Slack Incoming Webhook for #alerts
  2. In Vigilmon, go to Alert Channels → Webhook
  3. Paste the Slack URL and use this template:
{
  "text": "🚨 *{{ monitor.name }}* is DOWN\n{{ monitor.url }}\nStatus: {{ event.status }}\n<https://vigilmon.online|Open Vigilmon>"
}

Step 6: Test Everything

# 1. Verify health endpoint
curl -s https://yourdomain.com/health | jq .

# 2. Verify a partial endpoint
curl -s https://yourdomain.com/partials/dashboard | grep -c "class="

# 3. Use Vigilmon's "Test Alert" button to verify Slack/email delivery

# 4. Temporarily return 500 from /health and confirm the alert fires

What You Now Have

| Monitor | Catches | |---|---| | HTTP health check | Server crashes, DB failures, deploy errors | | Partial endpoint check | Key Htmx routes going down or erroring | | Heartbeat | Background jobs stopping or crashing |


Next Steps

  • Add a Vigilmon status page and link it from your app footer
  • Monitor your SSE or WebSocket endpoint separately if you use hx-ext="sse" or hx-ext="ws"
  • Use Vigilmon's response time history to track latency on your most-used partial routes

Found this useful? Vigilmon is free to start — sign up here and have your first monitor live in under 5 minutes.

Monitor your app with Vigilmon

Free plan — 5 monitors, no credit card required. Up and running in 60 seconds.

Start free →