Leptos is a full-stack Rust web framework with fine-grained reactivity — isomorphic rendering, server functions, and a reactive signal system all in one. It compiles to fast WASM on the client and runs natively on the server via Actix-web or Axum. But reactive Rust doesn't make you immune to downtime. Vigilmon gives you continuous HTTP monitoring, heartbeat checks, and instant alerting so you know the instant your Leptos app or its dependencies fail.
What You'll Build
- A
/healthendpoint in your Leptos backend - Vigilmon HTTP monitors for your app and health API
- A heartbeat monitor for scheduled background tasks
- Alert channels (email and Slack)
- An uptime badge in your Leptos web UI
Prerequisites
- A Leptos project with server-side rendering (SSR via Actix-web or Axum)
- A free Vigilmon account
Step 1: Add a Health Endpoint
Option A: Leptos with Actix-web
// src/main.rs
use actix_web::{web, App, HttpResponse, HttpServer};
use serde_json::json;
use std::collections::HashMap;
async fn health_check() -> HttpResponse {
let mut checks: HashMap<&str, String> = HashMap::new();
let mut degraded = false;
// Database check
match check_database().await {
Ok(_) => {
checks.insert("database", "ok".to_string());
}
Err(e) => {
checks.insert("database", format!("error: {}", e));
degraded = true;
}
}
// External API dependency check
match check_external_api().await {
Ok(_) => {
checks.insert("external_api", "ok".to_string());
}
Err(e) => {
checks.insert("external_api", format!("unreachable: {}", e));
degraded = true;
}
}
let status = if degraded { "degraded" } else { "ok" };
let body = json!({
"status": status,
"timestamp": chrono::Utc::now().to_rfc3339(),
"checks": checks,
});
if degraded {
HttpResponse::ServiceUnavailable().json(body)
} else {
HttpResponse::Ok().json(body)
}
}
async fn check_database() -> Result<(), Box<dyn std::error::Error>> {
// Replace with your actual database pool ping
// sqlx::query("SELECT 1").execute(&pool).await?;
Ok(())
}
async fn check_external_api() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::new();
client
.get("https://api.yourservice.com/ping")
.timeout(std::time::Duration::from_secs(3))
.send()
.await?;
Ok(())
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
let leptos_options = get_configuration(None).await.unwrap().leptos_options;
App::new()
.route("/health", web::get().to(health_check))
.leptos_routes(&leptos_options, routes, App)
})
.bind("0.0.0.0:3000")?
.run()
.await
}
Option B: Leptos with Axum
// src/main.rs
use axum::{routing::get, Json, Router};
use serde_json::{json, Value};
async fn health_handler() -> (axum::http::StatusCode, Json<Value>) {
let mut checks = std::collections::HashMap::new();
let mut degraded = false;
match check_database().await {
Ok(_) => { checks.insert("database", "ok"); }
Err(_) => { checks.insert("database", "error"); degraded = true; }
}
let status_code = if degraded {
axum::http::StatusCode::SERVICE_UNAVAILABLE
} else {
axum::http::StatusCode::OK
};
(status_code, Json(json!({
"status": if degraded { "degraded" } else { "ok" },
"timestamp": chrono::Utc::now().to_rfc3339(),
"checks": checks,
})))
}
async fn check_database() -> Result<(), sqlx::Error> {
// sqlx::query("SELECT 1").execute(&pool).await?;
Ok(())
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/health", get(health_handler))
.leptos_routes(/* ... */);
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
Add dependencies to Cargo.toml:
[dependencies]
actix-web = "4" # or axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde_json = "1"
chrono = { version = "0.4", features = ["serde"] }
reqwest = { version = "0.12", features = ["json"] }
Test it:
curl http://localhost:3000/health | jq .
# {"status":"ok","timestamp":"2026-06-29T...","checks":{"database":"ok","external_api":"ok"}}
Step 2: Configure Vigilmon HTTP Monitors
Log in to Vigilmon → Monitors → New Monitor:
Monitor 1: Leptos Web App
| Field | Value |
|---|---|
| URL | https://yourapp.com/ |
| Method | GET |
| Expected status | 200 |
| Expected body | A stable string from your SSR HTML output |
| Check interval | 1 minute |
Monitor 2: Health API
| Field | Value |
|---|---|
| URL | https://yourapp.com/health |
| Method | GET |
| Expected status | 200 |
| Expected body | "status":"ok" |
| Check interval | 1 minute |
Having separate monitors for the SSR frontend and the health API ensures you can distinguish between a WASM hydration failure and a backend database issue.
Step 3: Heartbeat for Background Tasks
Leptos server functions often have companion background workers. Use a heartbeat to detect silent failures:
// src/background.rs
use std::time::Duration;
use tokio::time;
pub async fn start_background_tasks() {
let heartbeat_url = std::env::var("VIGILMON_HEARTBEAT_URL").unwrap_or_default();
let mut interval = time::interval(Duration::from_secs(15 * 60)); // every 15 minutes
loop {
interval.tick().await;
match run_periodic_task().await {
Ok(_) => {
tracing::info!("Periodic task succeeded");
if !heartbeat_url.is_empty() {
ping_heartbeat(&heartbeat_url).await;
}
}
Err(e) => {
tracing::error!("Periodic task failed: {}", e);
// No ping sent → Vigilmon fires alert after grace window expires
}
}
}
}
async fn run_periodic_task() -> Result<(), Box<dyn std::error::Error>> {
// Example: invalidate stale reactive cache entries, run scheduled emails, etc.
tracing::info!("Running cache invalidation...");
Ok(())
}
async fn ping_heartbeat(url: &str) {
let client = reqwest::Client::new();
match client
.get(url)
.timeout(Duration::from_secs(5))
.send()
.await
{
Ok(_) => tracing::debug!("Heartbeat ping sent"),
Err(e) => tracing::warn!("Heartbeat ping failed (non-fatal): {}", e),
}
}
Spawn from your server entry point:
tokio::spawn(background::start_background_tasks());
Set up the heartbeat in Vigilmon:
- Monitors → New Heartbeat Monitor
- Set Expected interval to 30 minutes (2× the task interval)
- Add to your
.env:
VIGILMON_HEARTBEAT_URL=https://vigilmon.online/api/heartbeat/xxxxxxxx
Step 4: Embed an Uptime Badge in Your Leptos UI
Leptos components use view! macros — embedding a status badge is just an anchor and image element:
// src/components/status_badge.rs
use leptos::*;
const BADGE_URL: &str = "https://vigilmon.online/api/badge/your-monitor-id.svg";
const STATUS_URL: &str = "https://vigilmon.online/status/your-monitor-slug";
#[component]
pub fn StatusBadge() -> impl IntoView {
view! {
<a
href=STATUS_URL
target="_blank"
rel="noopener noreferrer"
aria-label="Service uptime status"
>
<img
src=BADGE_URL
alt="Uptime"
width="120"
height="20"
/>
</a>
}
}
Add it to your app shell:
// src/app.rs
use leptos::*;
use crate::components::status_badge::StatusBadge;
#[component]
pub fn App() -> impl IntoView {
view! {
<div>
<main>
// Your routes and reactive components
</main>
<footer class="footer">
<StatusBadge />
</footer>
</div>
}
}
Because Leptos uses fine-grained reactivity, the badge component only re-renders when its own signals change — it won't interfere with the rest of your reactive graph.
Step 5: Set Up Alert Channels
In Vigilmon's Alert Channels settings:
- Alert Channels → Email → add your on-call address
- Attach to both HTTP monitors and the heartbeat
Slack
- Create a Slack Incoming Webhook for
#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 6: Verify Everything Works
# 1. Confirm health returns 200
curl -s https://yourapp.com/health | jq .status
# 2. Simulate a database failure — change DB_URL to an invalid value, redeploy
# Expected: health returns 503; Vigilmon alert fires within ~2 minutes
# 3. Remove VIGILMON_HEARTBEAT_URL and let the scheduler run one tick
# Expected: heartbeat alert fires after the expected interval lapses
# 4. In Vigilmon → "Test Alert" to verify email and Slack delivery
Summary
| Monitor | What It Catches | |---|---| | HTTP app check | SSR/WASM serving failures, reverse proxy issues | | HTTP health API | Database, cache, and external API failures | | Heartbeat | Silent background task and scheduler failures |
Next Steps
- Create Vigilmon monitors for each Leptos deployment environment (dev, staging, production)
- Use Vigilmon's response time graphs to track SSR latency as your reactive component tree grows
- Set up status page alerts so your users see proactive outage notifications instead of discovering failures themselves
- Add server function-level tracing with
tracing-subscriberto correlate Vigilmon alerts with structured logs
Ready to get started? Sign up for Vigilmon free — have your first monitor running in under 5 minutes.