SvelteKit integration
SvelteKit analytics, error tracking and uptime
BugOtter works with SvelteKit on any adapter. One script tag in src/app.html gives you cookieless analytics and browser errors. @sentry/sveltekit, pointed at your BugOtter DSN, catches errors in load functions, actions and hooks. Uptime and a status page start when you add the URL.
- 1 tagin src/app.html
- Any adapterNode, Vercel, Netlify, Cloudflare
- 3 regionsuptime checks
What BugOtter gives a SvelteKit app
Analytics with no banner
One tag in the page shell. Client-side navigation counts, and the script loads once.Errors from load and actions
Two server hooks with@sentry/sveltekit. The script covers the browser.Past ad blockers
A small+server.tsroute forwards three paths from your own domain.
Analytics: one line in src/app.html
Add the script to the page shell
src/app.htmlis the HTML every page starts from, so it's the one place to add the tag.npx bugotter init --token bo_…finds it and adds the tag for you, and writes your keys to.env.local.src/app.html <!-- src/app.html --> <!doctype html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <script defer src="https://bugotter.com/bo.js" data-app="YOUR_APP_ID"></script> %sveltekit.head% </head> <body data-sveltekit-preload-data="hover"> <div style="display: contents">%sveltekit.body%</div> </body> </html>Client-side navigation is counted
SvelteKit's router moves between pages with
history.pushState. The script follows it, so each new path is a page view with its own time on page, scroll depth and Web Vitals. Keep signed-in pages out withdata-exclude="/app,/account"on the tag.Goals from any component
Call
window.bo?.track()in an event handler. Form submissions, includinguse:enhanceforms, file downloads and outbound links are counted with no code.src/routes/pricing/+page.svelte <!-- src/routes/pricing/+page.svelte --> <script lang="ts"> let plan = $state("pro"); </script> <button onclick={() => window.bo?.track("upgrade_click", { plan })}> Upgrade </button>Add the type to
src/app.d.ts(it already has adeclare globalblock):src/app.d.ts declare global { interface Window { bo?: { track(name: string, props?: Record<string, string | number | boolean>): void; visitorId(): string | null; }; } } export {};

- Payments on the chart. A green marker shows the day a big payment came in.
- Error spikes, too. A red marker lines up a bad day with the error behind it.
- Last period, dashed, so a drop or a launch spike is obvious at a glance.
- Bots kept out of every number, with the count one click away.
Get past ad blockers with a +server.ts route
A small endpoint on your own domain forwards three paths to BugOtter, and nothing else.
20–40%
of visitors are dropped by ad blockers for most analytics
// src/routes/_bo/[...path]/+server.ts
import { env } from "$env/dynamic/private";
import type { RequestHandler } from "./$types";
const BO = "https://bugotter.com";
export const fallback: RequestHandler = async ({ params, request }) => {
const key = env.BUGOTTER_PROXY_KEY;
const target: Record<string, string> = {
"bo.js": `${BO}/bo.js`,
"api/collect": `${BO}/api/collect?via=${key}`,
"api/errors": `${BO}/api/errors?via=${key}`,
};
const url = target[params.path];
if (!url) return new Response("Not found", { status: 404 });
// Pass on what BugOtter checks: who sent it, from which page, in which browser.
const headers = new Headers();
for (const h of ["content-type", "user-agent", "origin", "referer"]) {
const v = request.headers.get(h);
if (v) headers.set(h, v);
}
const res = await fetch(url, {
method: request.method,
headers,
body: request.method === "POST" ? await request.text() : undefined,
});
return new Response(res.body, {
status: res.status,
headers: {
"content-type": res.headers.get("content-type") ?? "text/plain",
"cache-control": res.headers.get("cache-control") ?? "no-store",
},
});
};Add BUGOTTER_PROXY_KEY to your environment, then change the tag in app.html to src="/_bo/bo.js".
- The endpoint needs a server adapter (Node, Vercel, Netlify or Cloudflare).
- If your
handlehook redirects signed-out visitors, let paths under/_bo/through.
Check it once: curl -sI https://yourapp.com/_bo/bo.js should answer 200. Your proxy key is on the app's Traffic tab in BugOtter. Keep the ?via= part: it proves visits came through your proxy, and it only lives in server config, never in the page.
Errors: @sentry/sveltekit with your BugOtter DSN
Browser errors already arrive from the script. For the server side, use Sentry's SvelteKit SDK and change only where it sends. You don't need a Sentry account, the wizard or the Sentry Vite plugin.
Install the SDK
terminal npm install @sentry/sveltekitTurn on server instrumentation and init
svelte.config.js // svelte.config.js (SvelteKit 2.31 or newer) const config = { kit: { adapter: adapter(), experimental: { instrumentation: { server: true } }, }, };Then init the SDK with your DSN, in its own file next to
hooks.server.ts:src/instrumentation.server.ts // src/instrumentation.server.ts import * as Sentry from "@sentry/sveltekit"; Sentry.init({ dsn: "YOUR_BUGOTTER_DSN", // from your app's page; it can only send errors dataCollection: { userInfo: false, cookies: false, httpBodies: [], databaseQueryData: false }, });On SvelteKit older than 2.31, put the same
Sentry.initcall at the top ofsrc/hooks.server.tsinstead. ThedataCollectionline is for SDK v11; on v10 usesendDefaultPii: false.Catch errors in hooks
handleErrorWithSentryreports unexpected errors from load functions, form actions and endpoints. Don't add ahooks.client.tsinit: the script covers the browser.src/hooks.server.ts // src/hooks.server.ts import * as Sentry from "@sentry/sveltekit"; import { sequence } from "@sveltejs/kit/hooks"; export const handleError = Sentry.handleErrorWithSentry(); export const handle = sequence(Sentry.sentryHandle() /*, your own handle */);
Kept
- Exceptions and
captureMessageevents, grouped into issues - Release, environment, the request path and runtime
- Navigation and failed-request breadcrumbs
- Your user id, hashed (never stored raw)
Accepted, then dropped
- Performance traces and spans
- Session replays and profiles
- Sessions, logs and metrics
- Console breadcrumbs and click text
On adapter-cloudflare, Sentry's SvelteKit SDK needs its extra Cloudflare setup from Sentry's docs. The Node, Vercel and Netlify adapters work as shown.

- A plain summary written from the evidence: page, browser, release and visits.
- Fix with agent hands the whole issue to Claude Code, Cursor or Codex.
- Where your code broke, mapped back to your file and line with your source maps.
Source maps from your adapter's output
In vite.config.ts, set build: { sourcemap: "hidden" }, so maps are written without a link to them in your scripts.
Then upload them after vite build from the folder your adapter deploys. Uploading from there matters: --delete then removes the maps from what actually goes live.
| Adapter | Upload from |
|---|---|
adapter-node | build/client |
adapter-static, adapter-netlify | build |
adapter-vercel | .vercel/output/static |
adapter-cloudflare | .svelte-kit/cloudflare |
"build": "vite build && npx bugotter sourcemaps build/client --delete"Paths come out as /_app/immutable/…, exactly as the browser loads them. The CLI needs BUGOTTER_PROJECT and BUGOTTER_KEY in the build environment. See the source maps guide for releases.
Uptime, a status page and deploys
3 regions
check your app from the moment you add the URL
Add a second check on an endpoint that touches your database, and expect ok to be true in the JSON:
// src/routes/api/health/+server.ts
import { json } from "@sveltejs/kit";
import { db } from "$lib/server/db";
export async function GET() {
await db.execute("select 1"); // prove the database answers, not just the server
return json({ ok: true });
}Deploys: issues name the deploy they started after.
- Vercel: tracked by themselves once connected.
- Netlify: add your app's deploy webhook as a Deploy succeeded notification.
- A Node server or Cloudflare: add
npx bugotter deployas the last deploy step.
Status page: at bugotter.com/status/your-app, with incidents you can post updates to and a README badge. Pro can use your own domain.

- Every day at a glance. Amber means a short outage that day.
- Response time, median and p95, and the slowest region.
- Three regions check your URL. An outage only counts when two agree.
Or ask your coding agent
Connect BugOtter to Claude Code, Cursor or Codex once over MCP. It signs in through your browser, read-only unless you allow changes. Then paste one line.
claude mcp add --transport http --scope user bugotter https://bugotter.com/api/mcpThen ask, in plain words:
Add BugOtter to this SvelteKit app: the script in src/app.html, @sentry/sveltekit server hooks with my BugOtter DSN, and source map upload for our adapter.
Add the BugOtter ad-blocker proxy as src/routes/_bo/[...path]/+server.ts and switch the script to /_bo/bo.js.
Using BugOtter, show the errors from our load functions this week and fix the most frequent one.
Cursor, VS Code, Codex and the rest have their own one-step setup at Docs: your agent (MCP). Every setup screen in BugOtter also has a Copy prompt for your agent button with your keys filled in.
Questions
Does BugOtter count SvelteKit client-side navigation?
Yes. SvelteKit's router changes pages with history.pushState, which the script listens for, along with the back button. Each new path is one page view. goto() with replaceState: true replaces the entry instead, so it isn't counted as a new page view.
Will the script run again on every navigation?
No. src/app.html is the shell SvelteKit sends once. The script in it runs on the first load and then follows client-side navigation itself.
Can a SvelteKit route start with an underscore?
Yes. In SvelteKit only files starting with + define routes, and a folder named _bo is an ordinary path segment, so src/routes/_bo/[...path]/+server.ts answers /_bo/bo.js and /_bo/api/collect.
My site uses adapter-static. Can I still proxy?
Not with a +server.ts route: on a static site there's no server to run it. Use your host's rewrites instead (Netlify, Vercel or a Cloudflare Pages Function). Everything else on this page works the same.
Do I need the Sentry Vite plugin?
No. sentrySvelteKit() in vite.config uploads source maps to Sentry and adds tracing to load functions. BugOtter takes source maps from its own CLI, and server errors are caught by the two hooks. If you already have the plugin, set autoUploadSourceMaps: false.
Where is the status page?
At bugotter.com/status/your-app once you turn it on. It shows uptime and incidents only, never errors or revenue. Pro can serve it from your own domain.
Sources
Checked Nov 4, 2026 against SvelteKit's docs on project structure, routing and hooks, and Sentry's SvelteKit manual setup. BugOtter isn't affiliated with Sentry or Svelte.
Watch every app you ship, from one calm list.
14 days of everything, no card needed. Plans from $10 a month, billed yearly.