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 Vigilmon → Monitors → 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:
- Monitors → New Heartbeat Monitor
- Set Expected interval to 25 hours
- 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:
- Alert Channels → Email → add your ops or on-call email
- Attach to all three monitors (site, health, heartbeat)
Slack
- Create a Slack Incoming Webhook for
#site-alerts - Alert Channels → Webhook → paste the URL
- 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.comusing Vigilmon's hosted status pages
Have your first Astro monitor live in under 5 minutes — sign up for Vigilmon free.