tutorial

Monitoring Your Astro Site with Vigilmon: API Endpoints, Islands & Uptime Alerts

Add production-grade monitoring to an Astro site — health API endpoints, heartbeat checks for scheduled tasks, and instant alerts when your content site or SSR app goes down.

Astro's flexible architecture — static output, SSR, server islands, Edge middleware — means your failure surface shifts based on how you've deployed. A purely static build hosted on Netlify can go down if the CDN has an outage. An SSR Astro app on Vercel can start returning 500s because of a broken database connection. An Astro API endpoint can time out without a single frontend page showing any symptoms. Vigilmon monitors all of these failure modes continuously. This tutorial walks you through wiring Astro into Vigilmon for complete production visibility.

What You'll Build

  • An Astro API endpoint (/api/health) for deep health checks
  • Vigilmon HTTP monitors for the site and the health endpoint
  • A heartbeat for Astro scheduled jobs or external cron triggers
  • Alert routing to email and Slack
  • A status badge in your Astro layout

Prerequisites

  • An Astro project (Astro 4+ recommended)
  • Astro SSR adapter if using server-side features (Vercel, Node, Cloudflare)
  • A free Vigilmon account

Step 1: Add an Astro API Health Endpoint

Astro's API routes live in src/pages/api/ and export HTTP method handlers. Add a health endpoint at src/pages/api/health.ts:

// src/pages/api/health.ts
import type { APIRoute } from "astro";

export const prerender = false; // Always server-rendered, never cached

export const GET: APIRoute = async ({ locals }) => {
  const checks: Record<string, string> = {};
  let degraded = false;

  // Database check — adapt to your ORM or driver
  try {
    // Example with Drizzle + Postgres
    // await locals.db.execute(sql`SELECT 1`);
    checks.database = "ok";
  } catch (err) {
    checks.database = `error: ${(err as Error).message}`;
    degraded = true;
  }

  // External API check
  try {
    const res = await fetch("https://api.contentful.com/", {
      signal: AbortSignal.timeout(3000),
    });
    checks.cms = res.status < 500 ? "ok" : `http_${res.status}`;
  } catch {
    checks.cms = "unreachable";
  }

  // Edge/environment check
  checks.runtime = typeof EdgeRuntime !== "undefined" ? "edge" : "node";

  const body = JSON.stringify({
    status: degraded ? "degraded" : "ok",
    timestamp: new Date().toISOString(),
    checks,
  });

  return new Response(body, {
    status: degraded ? 503 : 200,
    headers: {
      "Content-Type": "application/json",
      "Cache-Control": "no-store, no-cache, must-revalidate",
    },
  });
};

Static Astro sites: If your output is static, you don't have server-rendered API routes. Instead, deploy a lightweight health check sidecar (see Step 1b) or rely solely on the HTTP monitor for the static site.

Step 1b: Health Sidecar for Static Sites

For purely static Astro builds, run a minimal Node server for health checks:

// health.js (runs alongside your static host or in a separate process)
import http from "http";

const server = http.createServer(async (req, res) => {
  if (req.url !== "/health") {
    res.writeHead(404).end();
    return;
  }

  const checks = { static_host: "ok" };

  // Check that your static host is reachable
  try {
    const response = await fetch(process.env.SITE_URL + "/", {
      signal: AbortSignal.timeout(5000),
    });
    checks.site = response.ok ? "ok" : `http_${response.status}`;
  } catch (err) {
    checks.site = `error: ${err.message}`;
  }

  res.writeHead(200, { "Content-Type": "application/json" });
  res.end(JSON.stringify({ status: "ok", checks, timestamp: new Date().toISOString() }));
});

server.listen(process.env.HEALTH_PORT ?? 3001, () =>
  console.log(`[health] Listening on :${process.env.HEALTH_PORT ?? 3001}`)
);

Step 2: Test the Endpoint Locally

# For SSR Astro
npm run dev
curl http://localhost:4321/api/health | jq .
# {"status":"ok","timestamp":"2026-06-29T...","checks":{"database":"ok","cms":"ok","runtime":"node"}}

Step 3: Set Up Vigilmon HTTP Monitors

Log in to VigilmonMonitors → New Monitor:

Monitor 1: Astro Site

| Field | Value | |---|---| | URL | https://yoursite.com/ | | Method | GET | | Expected status | 200 | | Expected body | A stable string from your HTML (e.g. your site title) | | Check interval | 1 minute |

This catches CDN failures, broken static deploys, and adapter-level errors.

Monitor 2: Health API Endpoint

| Field | Value | |---|---| | URL | https://yoursite.com/api/health | | Method | GET | | Expected status | 200 | | Check interval | 1 minute |

The two-monitor setup matters: your site can serve cached HTML while the database is completely unreachable — only the health endpoint will catch that class of failure.


Step 4: Heartbeat for Scheduled Tasks

Astro sites often have external cron jobs — content rebuilds, sitemap generation, email digests. Wire those to a Vigilmon heartbeat.

Vercel Cron + Astro API Route

// vercel.json
{
  "crons": [
    {
      "path": "/api/cron/rebuild",
      "schedule": "0 3 * * *"
    }
  ]
}
// src/pages/api/cron/rebuild.ts
import type { APIRoute } from "astro";

export const prerender = false;

export const GET: APIRoute = async ({ request }) => {
  // Verify the request comes from Vercel Cron
  const authHeader = request.headers.get("authorization");
  if (authHeader !== `Bearer ${import.meta.env.CRON_SECRET}`) {
    return new Response(JSON.stringify({ error: "Unauthorized" }), { status: 401 });
  }

  try {
    await triggerContentRebuild();

    // Ping Vigilmon heartbeat
    const heartbeatUrl = import.meta.env.VIGILMON_HEARTBEAT_URL;
    if (heartbeatUrl) {
      await fetch(heartbeatUrl, { signal: AbortSignal.timeout(5000) }).catch(() => {});
    }

    return new Response(JSON.stringify({ ok: true }), { status: 200 });
  } catch (err) {
    return new Response(JSON.stringify({ error: String(err) }), { status: 500 });
  }
};

async function triggerContentRebuild() {
  // Your rebuild or content sync logic here
}

Netlify Scheduled Functions

// netlify/functions/nightly-sync.mjs
export default async (req) => {
  try {
    await runSync();

    const heartbeatUrl = process.env.VIGILMON_HEARTBEAT_URL;
    if (heartbeatUrl) {
      await fetch(heartbeatUrl).catch(() => {});
    }
  } catch (err) {
    console.error("[nightly-sync] Failed:", err);
  }
};

export const config = {
  schedule: "0 2 * * *",
};

Create the heartbeat in Vigilmon:

  1. Monitors → New Heartbeat Monitor
  2. Set Expected interval to 25 hours
  3. Add to .env:
VIGILMON_HEARTBEAT_URL=https://vigilmon.online/api/heartbeat/xxxxxxxx

Step 5: Add a Status Badge to Your Astro Layout

---
// src/components/StatusBadge.astro
// No server-side props needed — loads SVG badge directly
const BADGE_URL = "https://vigilmon.online/api/badge/your-monitor-id.svg";
const STATUS_URL = "https://vigilmon.online/status/your-monitor-slug";
---

<a
  href={STATUS_URL}
  target="_blank"
  rel="noopener noreferrer"
  aria-label="Service status"
>
  <img
    src={BADGE_URL}
    alt="Uptime status"
    width="120"
    height="20"
    loading="lazy"
  />
</a>

Add to your layout:

---
// src/layouts/BaseLayout.astro
import StatusBadge from "../components/StatusBadge.astro";
---

<html>
  <body>
    <slot />
    <footer>
      <StatusBadge />
    </footer>
  </body>
</html>

Step 6: Configure Alert Channels

In Vigilmon's Alert Channels settings:

Email

  1. Alert Channels → Email → add your ops or on-call email
  2. Attach to all three monitors (site, health, heartbeat)

Slack

  1. Create a Slack Incoming Webhook for #site-alerts
  2. Alert Channels → Webhook → paste the URL
  3. Payload template:
{
  "text": "🚨 *{{ monitor.name }}* is DOWN\n{{ monitor.url }}\nStatus: {{ event.status }}\n<https://vigilmon.online|Open Vigilmon>"
}

Step 7: Test End-to-End

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

# 2. Simulate a backend failure (bad DB URL) and redeploy
# Expected: /api/health returns 503, Vigilmon alerts within 2 minutes

# 3. Disable VIGILMON_HEARTBEAT_URL, wait past the grace window
# Expected: heartbeat alert fires

# 4. Vigilmon → "Test Alert" → confirm Slack/email delivery

Summary

| Monitor | What It Catches | |---|---| | HTTP site check | CDN outages, broken builds, adapter failures | | HTTP health endpoint | Database, CMS, and external API failures | | Heartbeat | Silent cron failures, missed content rebuilds |


Deployment Notes by Adapter

| Adapter | Key Consideration | |---|---| | Vercel | Use export const prerender = false on health route; add to vercel.json crons | | Netlify | Use Netlify Scheduled Functions for heartbeat pings | | Cloudflare | Add Cache-Control: no-store headers to health route; health route bypasses cache rules | | Node (self-hosted) | Health route runs on same process — add a dedicated health.js sidecar for true isolation |


Next Steps

  • Create separate monitors for staging and production
  • Use Vigilmon's response time graphs to catch build-induced latency regressions
  • Add a public status page at status.yoursite.com using Vigilmon's hosted status pages

Have your first Astro monitor live in under 5 minutes — sign up for Vigilmon free.

Monitor your app with Vigilmon

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

Start free →