SvelteKit is a metaframework, and that word does real work when something breaks. The same codebase deploys as a long-running Node server, a fleet of serverless functions, an edge worker, or a folder of static HTML — depending on one line in svelte.config.js. Your monitoring has to match the adapter, because the adapter decides how your app fails: a Node process dies and takes every route with it, a serverless function cold-starts or times out per-route, and a static build can't crash but can absolutely serve a broken deploy.

This guide covers the monitors every SvelteKit app needs regardless of adapter, the per-adapter failure modes worth a dedicated check, and the one SvelteKit-specific gotcha that standard uptime monitoring is structurally blind to: the ORIGIN misconfiguration that returns 403 on every form submission while every GET in your app returns a healthy 200.

The four monitors every SvelteKit app needs

1. A server-rendered page

Pick a page that actually runs your server code — one with a +page.server.ts load function. This single check exercises the full SSR stack: the adapter runtime, your hooks.server.ts, and the load function's data dependencies. Don't pick a page you've prerendered; a static file will happily return 200 while your server is a smoking crater.

There's a structural reason to care about this one: hooks.server.ts runs on every request. An exception in your handle hook — a bad import, a throwing auth check, a database client initialized at module scope against an unreachable host — doesn't break one route. It breaks all of them, the same way a failed middleware takes down an entire Next.js app. One SSR monitor catches the whole class.

2. A prerendered page (if you prerender)

Routes with export const prerender = true are built to static files and served without touching your server. They fail on a different axis — DNS, TLS, CDN config, a deploy that shipped an empty build — so they need their own monitor. The pair is diagnostic: SSR down + prerendered up means your server or its dependencies; both down means DNS, TLS, or the platform.

3. A dedicated health endpoint

A standalone +server.ts endpoint that checks hard dependencies and reports honestly:

// src/routes/api/health/+server.ts
import { json } from '@sveltejs/kit';
import { db } from '$lib/server/db';

export const GET = async () => {
  const checks = { database: 'unknown' };
  let healthy = true;

  try {
    await db.execute('SELECT 1');
    checks.database = 'ok';
  } catch {
    checks.database = 'down';
    healthy = false;
  }

  return json(
    { status: healthy ? 'ok' : 'degraded', checks },
    { status: healthy ? 200 : 503 }
  );
};

Keep dependency probes cheap and short-timeout — this runs every minute. The usual health endpoint rules apply: hard dependencies gate the status code, soft dependencies get reported in the body without failing the check, and a keyword assertion on "status":"ok" stops a proxy's generic 200 error page from impersonating health.

4. Something POST-shaped

This is the SvelteKit-specific one, and it's the section below.

The ORIGIN gotcha: every form 403s, every monitor stays green

SvelteKit ships CSRF protection that rejects form submissions whose origin doesn't match the app's. With adapter-node behind a reverse proxy — nginx, Caddy, Traefik, a load balancer — the Node process sees the proxy's internal address as its origin unless you tell it otherwise. The result is a failure mode with a perfect disguise:

  • Every GET route returns 200. Pages render. The health endpoint is green.
  • Every form action returns 403 "Cross-site POST form submissions are forbidden" — login, signup, checkout, contact form, all of it.

The fix is one environment variable on the server process: ORIGIN=https://yourdomain.com (or correctly forwarding and trusting the Host and X-Forwarded-* headers). The trap is that it regresses easily — a new deployment target, a rewritten proxy config, a container rebuilt without the env var — and GET-based uptime monitoring is structurally blind to it.

Two ways to cover it. The direct one: a monitor that POSTs to a route that responds without side effects. The indirect one: keyword monitoring on a page whose rendered content proves a form flow works — for example, a status string on a page behind your login flow via a synthetic account. Either way, the point is that at least one monitor exercises the POST path. If a config change 403s your forms at midnight, you want the alert, not the Monday support tickets.

Per-adapter failure modes

adapter-node: you own the process

With adapter-node you're running a plain Node server, which means the failure modes of every long-running Node process plus the ops around it:

  • The process dies and nothing restarts it. An uncaught exception or OOM kill ends the process; without a systemd unit with Restart=always, a Docker restart policy, or pm2, it stays dead. External monitoring is the verification layer — the restart policy is the fix, the monitor proves it worked.
  • The proxy outlives the app. nginx returning its own 502 page means the upstream is gone. Your monitor sees the 502; make sure it's checking through the proxy (the public URL), not hitting the Node port directly from localhost.
  • Slow leaks. Memory growth over days shows up as rising response times before it becomes an outage. Watch your monitor's response-time trend, not just up/down.
  • ORIGIN / body size env vars. ORIGIN as above; BODY_SIZE_LIMIT if users upload anything bigger than the default cap.

Self-hosting the whole stack? The Docker and self-hosted guide covers the layer under the app.

Serverless adapters (Vercel, Netlify): no process to die, new things instead

On serverless platforms the dead-process class disappears — the platform runs your functions on demand. What you get instead:

  • Cold starts. An infrequently-hit route can take a couple of seconds on first invocation. Set monitor timeout thresholds with that in mind, and use consecutive-check confirmation so one cold start doesn't page you.
  • Per-function timeouts. A load function that waits on a slow upstream can hit the platform's duration ceiling and 504 — on that route only. Monitor your slowest important route, not just the homepage.
  • Platform incidents. Your code is fine; the platform isn't. External monitoring tells you it's down; the platform status page tells you whose fault it is. You need both, and they disagree more often than you'd hope.

adapter-cloudflare: edge workers

Cloudflare's model has its own gotchas — CPU-time limits rather than wall-clock, bindings that fail at runtime when misconfigured, and the subtlety that Cloudflare monitoring Cloudflare from inside Cloudflare proves little. We covered the details in the Cloudflare Workers guide — it applies directly to SvelteKit on adapter-cloudflare.

adapter-static: nothing to crash, plenty to break

Fully prerendered apps are static sites and should be monitored like them: DNS, TLS expiry, CDN behavior, and — the one people skip — deploy verification. A build that succeeds but ships broken HTML serves 200s forever. A keyword check on a string that only a correct build produces catches it. Full treatment in the static site monitoring guide.

One more lie to watch for: streamed load functions

SvelteKit lets a load function return unresolved promises so the page shell renders immediately and slow data streams in. It's great for perceived performance and quietly bad for monitoring: the page returns 200 before the streamed data resolves. If the streamed promise rejects, your user sees a broken section — and your status-code monitor sees a healthy page. If a streamed region is business-critical, either await it in the load function (so failures become real 500s) or put a content check on output that only renders when the data arrived.

Set it up in ten minutes

  • Create a CronAlert account — the SSR page, prerendered page, and health endpoint monitors all fit on the free plan (25 monitors, 3-minute checks). Keyword and content checks are on Pro at $5/mo.
  • Add the /api/health endpoint above and point a monitor at it.
  • Add monitors for one SSR route and one prerendered route.
  • If you run adapter-node behind a proxy: verify ORIGIN is set, then add a POST-path or keyword check so a regression can't hide behind green GETs.
  • Route alerts to Slack, email, or your on-call tool, and confirm delivery with a test alert.

Frequently asked questions

What should I monitor in a SvelteKit app?

One SSR page (exercises hooks and load functions), one prerendered page (exercises the static path), a /api/health endpoint with dependency checks, and one POST-shaped check for the form-action path. Adapter-specific extras per the sections above.

Why do my forms return 403 while pages load fine?

SvelteKit's CSRF origin check is failing — almost always a missing ORIGIN env var behind a reverse proxy with adapter-node. GET monitors can't see it; POST or keyword checks can.

How do I add a health check endpoint?

A +server.ts GET handler that probes hard dependencies with short timeouts and returns 503 when one is down. Code above.

Do prerendered pages need monitoring?

Yes — they fail on the DNS/TLS/CDN/bad-deploy axis instead of the server axis. Monitoring both kinds separately turns an outage into a diagnosis.

Does the adapter change what I monitor?

Entirely. Node: process death, proxy config, leaks. Serverless: cold starts and per-route timeouts. Static: content correctness and the delivery chain.

Monitor the app you actually deployed

SvelteKit's flexibility means two teams with identical codebases can have disjoint failure modes. Monitor the deployment, not the framework: the SSR path, the static path, the health endpoint, and the POST path that GETs can't vouch for. Set up CronAlert free — the core four monitors take ten minutes, and the first time ORIGIN regresses you'll hear about it from an alert instead of a user.

Related reading: Next.js and Vercel monitoring, static site monitoring, designing health check endpoints, and Cloudflare Workers monitoring.