SvelteKit apps are fast and flexible — but that flexibility means failures hide in unusual places. A Cloudflare Worker can serve stale cached HTML while your database is unreachable. A Vercel Edge Function can silently time out. A SvelteKit +server.ts route can start returning 500s on one region while other regions look fine. Vigilmon watches all of this continuously. This tutorial shows you how to wire SvelteKit into Vigilmon for uptime monitoring, heartbeat checks, and alert routing.
What You'll Build
- A
+server.tshealth route that checks database and external dependencies - Vigilmon HTTP monitors for both your SvelteKit app and any API routes
- A cron heartbeat using the
cronpackage for scheduled tasks - Deployment health tips for Cloudflare Workers and Vercel
- Email and Slack alert channels
Prerequisites
- SvelteKit project (SvelteKit 2.x recommended)
- A free Vigilmon account
- Optionally: a database (Postgres, PlanetScale, Turso, etc.)
Step 1: Create the Health Server Route
SvelteKit's +server.ts files handle HTTP requests server-side. Add one at src/routes/api/health/+server.ts:
// src/routes/api/health/+server.ts
import { json } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";
export const GET: RequestHandler = async ({ platform }) => {
const checks: Record<string, string> = {};
let degraded = false;
// Database check — adapt to your client (Drizzle, Prisma, Turso, etc.)
try {
// Example with a Turso/libSQL client from the platform env
// await platform?.env?.DB.execute("SELECT 1");
checks.database = "ok";
} catch (err) {
checks.database = `error: ${(err as Error).message}`;
degraded = true;
}
// External dependency check (e.g. a third-party API)
try {
const res = await fetch("https://api.stripe.com/v1/", {
signal: AbortSignal.timeout(3000),
});
checks.stripe = res.status < 500 ? "ok" : `http_${res.status}`;
} catch {
checks.stripe = "unreachable";
// Note: don't degrade for third-party deps unless critical
}
return json(
{
status: degraded ? "degraded" : "ok",
timestamp: new Date().toISOString(),
checks,
},
{ status: degraded ? 503 : 200 }
);
};
// Prevent caching of the health endpoint
export const config = {
isr: { expiration: 0 }, // Vercel ISR: never cache
runtime: "nodejs", // Force Node runtime, not Edge, for DB access
};
Test it locally:
curl http://localhost:5173/api/health | jq .
# {"status":"ok","timestamp":"2026-06-29T...","checks":{"database":"ok","stripe":"ok"}}
Step 2: Add Vigilmon HTTP Monitors
Log in to Vigilmon and create two monitors:
Monitor 1: SvelteKit App
| Field | Value |
|---|---|
| URL | https://app.yourdomain.com/ |
| Method | GET |
| Expected status | 200 |
| Expected body | Any stable string from your <h1> or <title>
|
| Check interval | 1 minute |
Monitor 2: Health API Route
| Field | Value |
|---|---|
| URL | https://app.yourdomain.com/api/health |
| Method | GET |
| Expected status | 200 |
| Check interval | 1 minute |
Having both monitors is important: the homepage monitor catches rendering failures; the health API monitor catches database or dependency failures that don't immediately break the HTML response.
Step 3: Heartbeat for Scheduled Tasks
If you have scheduled tasks (data syncs, digest emails, cleanup jobs), wire them to a Vigilmon heartbeat so you know when they stop running.
Install node-cron
npm install node-cron
npm install --save-dev @types/node-cron
Create a Scheduled Task with a Heartbeat
// src/lib/server/scheduler.ts
import cron from "node-cron";
const VIGILMON_HEARTBEAT_URL = process.env.VIGILMON_HEARTBEAT_URL ?? "";
async function pingHeartbeat(): Promise<void> {
if (!VIGILMON_HEARTBEAT_URL) return;
try {
await fetch(VIGILMON_HEARTBEAT_URL, {
signal: AbortSignal.timeout(5000),
});
} catch {
// Never let monitoring failure break the job
}
}
export function startScheduler(): void {
// Example: nightly data sync at 02:00
cron.schedule("0 2 * * *", async () => {
try {
await runNightlySync();
await pingHeartbeat(); // ping only on success
} catch (err) {
console.error("[scheduler] nightly sync failed:", err);
// Vigilmon will alert after the expected interval passes without a ping
}
});
console.log("[scheduler] Started");
}
async function runNightlySync(): Promise<void> {
// Your actual sync logic here
}
Start the scheduler from your hooks.server.ts:
// src/hooks.server.ts
import { startScheduler } from "$lib/server/scheduler";
// Only start in non-serverless environments
if (process.env.NODE_ENV === "production" && !process.env.VERCEL) {
startScheduler();
}
Create a Heartbeat monitor in Vigilmon:
- Monitors → New Heartbeat Monitor
- Set the expected interval to 25 hours (gives the nightly job a 1-hour grace)
- Copy the ping URL into your
.env:
VIGILMON_HEARTBEAT_URL=https://vigilmon.online/api/heartbeat/xxxxxxxx
Step 4: Deployment Health Tips
Cloudflare Workers / Pages
Cloudflare caches aggressively. Add cache-control headers to your health route:
// src/routes/api/health/+server.ts
export const GET: RequestHandler = async () => {
// ... checks ...
return json(
{ status: "ok", ... },
{
headers: {
"Cache-Control": "no-store, no-cache, must-revalidate",
"CDN-Cache-Control": "no-store",
},
}
);
};
Also set a Cloudflare health check rule to bypass cache for /api/health*.
For Cloudflare Workers scheduled tasks (using scheduled event handler), ping the heartbeat at the end of each successful run:
// worker.js
export default {
async scheduled(event, env, ctx) {
ctx.waitUntil(runTask(env));
},
};
async function runTask(env) {
try {
await doWork(env);
await fetch(env.VIGILMON_HEARTBEAT_URL);
} catch (err) {
console.error("Task failed:", err);
}
}
Vercel
For Vercel deployments, use Vercel Cron Jobs + a heartbeat:
// vercel.json
{
"crons": [
{
"path": "/api/cron/sync",
"schedule": "0 2 * * *"
}
]
}
// src/routes/api/cron/sync/+server.ts
import { json } from "@sveltejs/kit";
import type { RequestHandler } from "./$types";
export const GET: RequestHandler = async ({ request }) => {
// Verify this is coming from Vercel Cron
const authHeader = request.headers.get("authorization");
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
return json({ error: "Unauthorized" }, { status: 401 });
}
try {
await runSync();
// Ping Vigilmon heartbeat
const heartbeatUrl = process.env.VIGILMON_HEARTBEAT_URL;
if (heartbeatUrl) {
await fetch(heartbeatUrl).catch(() => {});
}
return json({ ok: true });
} catch (err) {
return json({ error: String(err) }, { status: 500 });
}
};
Step 5: Configure Alert Channels
In Vigilmon's Alerts settings:
- Alert Channels → Email → add your ops email
- Attach to both HTTP monitors and the heartbeat monitor
Slack Webhook
- Create a Slack Incoming Webhook for
#alerts - Alert Channels → Webhook → paste the URL
- Use this payload template:
{
"text": "🚨 *{{ monitor.name }}* is DOWN\n{{ monitor.url }}\nStatus: {{ event.status }}\n<https://vigilmon.online|Open Vigilmon>"
}
Step 6: Test End-to-End
# 1. Verify the health route
curl -s https://app.yourdomain.com/api/health | jq .
# 2. Simulate a failure: break your DB connection string and redeploy
# Expected: health route returns 503, Vigilmon alerts within 2 minutes
# 3. Stop the scheduler, wait past the heartbeat window
# Expected: Vigilmon fires a heartbeat alert
# 4. Use Vigilmon "Test Alert" → confirm Slack/email delivery
What You Now Have
| Monitor | Catches |
|---|---|
| HTTP app check | Render failures, broken deploys, CDN issues |
| HTTP health route | Database failures, external dependency outages |
| Cron heartbeat | Scheduled task crashes, cron misconfiguration |
Next Steps
- Embed the Vigilmon status badge in your SvelteKit footer component
- Create per-environment monitors (staging vs production) to catch issues before they hit users
- Use Vigilmon's response time graphs to correlate latency spikes with deploys
Found this useful? Vigilmon is free to start — sign up here and have your first monitor live in under 5 minutes.
Top comments (0)