DEV Community

Vigilmon
Vigilmon

Posted on

Setting Up Uptime Monitoring for Remix Apps with Vigilmon

Setting Up Uptime Monitoring for Remix Apps with Vigilmon

Remix is a full-stack React framework built around web standards — loader functions, actions, and nested routes make it a joy to build with. But when a Remix app goes down, you want to know immediately, not when a user tweets at you.

This tutorial shows you how to add a health-check route to a Remix app and connect it to Vigilmon for multi-region uptime monitoring with email and webhook alerts.


What You'll Build

  • A Remix _health route that probes real dependencies
  • A Vigilmon monitor checking the route every 5 minutes from multiple regions
  • Alert channels (email + Slack webhook) for instant down/up notifications

Prerequisites

  • Node.js 18+ with a Remix project (or npx create-remix@latest)
  • A free Vigilmon account (5 monitors free, no credit card)

Step 1: Create a Health-Check Route in Remix

Remix uses file-based routing. Create a dedicated health route that bypasses the React rendering pipeline entirely:

app/
  routes/
    _health.tsx       ← new file
    _index.tsx
Enter fullscreen mode Exit fullscreen mode
// app/routes/_health.tsx
import type { LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";

export async function loader({ request }: LoaderFunctionArgs) {
  const health = {
    status: "ok",
    timestamp: new Date().toISOString(),
    env: process.env.NODE_ENV,
  };

  return json(health, {
    status: 200,
    headers: {
      "Cache-Control": "no-store",
    },
  });
}

// No default export — this route returns JSON only, no UI
Enter fullscreen mode Exit fullscreen mode

The absence of a default export means Remix won't try to render a React component for this route; it's a pure data endpoint.

Test it:

npx remix dev
curl http://localhost:3000/_health
# {"status":"ok","timestamp":"...","env":"development"}
Enter fullscreen mode Exit fullscreen mode

Why _health instead of health? The leading underscore tells Remix this is a "pathless" route segment — you access it at /_health. You can also use health.tsx for /health — either works fine with Vigilmon.


Step 2: Add Real Dependency Checks

A 200-always endpoint only tells you the Node process is alive. Add checks that test your actual data layer:

// app/routes/_health.tsx
import type { LoaderFunctionArgs } from "@remix-run/node";
import { json } from "@remix-run/node";
import { db } from "~/db.server";          // your Prisma / Drizzle client
import { redis } from "~/redis.server";    // your Redis client (if any)

interface CheckResult {
  ok: boolean;
  latencyMs?: number;
  error?: string;
}

async function checkDatabase(): Promise<CheckResult> {
  const start = Date.now();
  try {
    // Prisma example:
    await db.$queryRaw`SELECT `4;
    // Drizzle example:
    // await db.execute(sql`SELECT 1`);
    return { ok: true, latencyMs: Date.now() - start };
  } catch (err) {
    return { ok: false, error: String(err) };
  }
}

async function checkRedis(): Promise<CheckResult> {
  if (!redis) return { ok: true };          // skip if not configured
  const start = Date.now();
  try {
    await redis.ping();
    return { ok: true, latencyMs: Date.now() - start };
  } catch (err) {
    return { ok: false, error: String(err) };
  }
}

export async function loader({ request }: LoaderFunctionArgs) {
  const [database, cache] = await Promise.allSettled([
    checkDatabase(),
    checkRedis(),
  ]);

  const dbResult = database.status === "fulfilled" ? database.value : { ok: false };
  const cacheResult = cache.status === "fulfilled" ? cache.value : { ok: false };

  const allOk = dbResult.ok && cacheResult.ok;

  return json(
    {
      status: allOk ? "ok" : "degraded",
      checks: { database: dbResult, cache: cacheResult },
      timestamp: new Date().toISOString(),
    },
    {
      status: allOk ? 200 : 503,
      headers: { "Cache-Control": "no-store" },
    }
  );
}
Enter fullscreen mode Exit fullscreen mode

Vigilmon sees the 503 and fires an alert — no manual threshold configuration needed.


Step 3: Deploy Your Remix App

Remix runs on multiple targets. The health route works identically across all of them.

Vercel (zero config):

npm install @remix-run/vercel vercel
npx vercel deploy --prod
Enter fullscreen mode Exit fullscreen mode

Fly.io (Dockerfile-based):

fly launch          # generates fly.toml + Dockerfile
fly deploy
Enter fullscreen mode Exit fullscreen mode

Railway / Render: Connect your GitHub repo and set the start command to npm run start.

After deployment, note your production URL, e.g. https://my-remix-app.vercel.app.


Step 4: Create the Vigilmon Monitor

  1. Log in at vigilmon.online and click + New Monitor.
  2. Monitor Type: HTTP / HTTPS.
  3. URL: https://my-remix-app.vercel.app/_health
  4. Check Interval: 5 minutes.
  5. Expected Status: 2xx (or 200 specifically).
  6. Click Save Monitor.

Vigilmon immediately begins polling from geographically distributed nodes. The multi-region consensus model means a single regional network fault won't trigger a false alert — it only fires when a majority of probes agree the site is unreachable.


Step 5: Configure Alert Channels

Email

Settings → Alert Channels → + Add Channel → Email

Enter your email address. Assign it to the monitor under Monitors → Edit → Alert Channels. You'll receive:

  • A "DOWN" email within minutes of the first failed check consensus
  • An "UP" email once the endpoint recovers, with total downtime duration

Slack Webhook

  1. In your Slack workspace: Apps → Incoming Webhooks → Add to Slack, pick a channel, copy the URL.
  2. In Vigilmon: Settings → Alert Channels → + Add Channel → Webhook, paste the URL.
  3. Assign it to your monitor.

Sample down alert your Slack channel receives:

{
  "monitor": "my-remix-app /_health",
  "status": "down",
  "reason": "HTTP 503",
  "body_excerpt": "{\"status\":\"degraded\",\"checks\":{\"database\":{\"ok\":false}}}",
  "regions_failed": ["us-east", "eu-west"],
  "checked_at": "2024-01-15T10:31:00Z"
}
Enter fullscreen mode Exit fullscreen mode

Step 6: Multi-Region Strategy for Remix

Remix on edge runtimes (Cloudflare Workers, Vercel Edge) is often deployed to tens of regions simultaneously. Consider adding region-aware health metadata:

export async function loader({ request }: LoaderFunctionArgs) {
  // Cloudflare: request.cf?.colo; Vercel: process.env.VERCEL_REGION
  const region =
    (request as any).cf?.colo ??
    process.env.VERCEL_REGION ??
    process.env.FLY_REGION ??
    "unknown";

  const allOk = true; // replace with your checks
  return json(
    { status: allOk ? "ok" : "degraded", region, timestamp: new Date().toISOString() },
    { status: allOk ? 200 : 503, headers: { "Cache-Control": "no-store" } }
  );
}
Enter fullscreen mode Exit fullscreen mode

If you use Remix on multiple custom domains (e.g. per-tenant subdomains), add a separate Vigilmon monitor for each — the free tier covers up to 5 monitors.


Step 7: Test the Full Pipeline

Simulate a failure by temporarily forcing a 503:

// _health.tsx — temporary, for testing only
export async function loader() {
  return json({ status: "down", reason: "test failure" }, { status: 503 });
}
Enter fullscreen mode Exit fullscreen mode

Deploy, wait ≥ 5 minutes, and confirm the alert fires. Then revert and confirm the recovery notification.


Step 8: Add a Status Badge (Optional)

Show your uptime status directly in your README:

[![Uptime](https://vigilmon.online/badge/<monitor-id>)](https://vigilmon.online)
Enter fullscreen mode Exit fullscreen mode

Find your monitor ID in the dashboard URL. The badge auto-updates on every check cycle.


Remix-Specific Tips

Loader caching: Always set Cache-Control: no-store on health responses. Remix loaders are sometimes cached by CDNs — a cached 200 will mask a real outage.

Resource routes vs. standard routes: The approach above uses a resource route (no default export). This is the recommended pattern — it keeps health checks off the React rendering critical path and avoids any risk of client-side hydration errors.

Nested routes: If your app uses a root loader that fetches user session data, don't inherit it in the health route. Keep health checks stateless and fast.

Scheduled jobs / background queues: If your Remix app depends on a worker process (Inngest, BullMQ), add a separate endpoint or Vigilmon monitor for the queue health — the web health route won't catch a stalled job processor.


Summary

You've added production-grade monitoring to your Remix application in a few steps:

  1. A _health resource route that tests real dependencies and returns 503 on failure
  2. Vigilmon polling every 5 minutes from multiple global regions
  3. Email and Slack webhook alerts with automatic recovery notifications

The Vigilmon free tier at vigilmon.online requires no credit card and supports up to 5 monitors — perfect for a Remix project in early production.


Using Remix in an interesting stack? Let me know in the comments what you're monitoring.

Top comments (0)