SvelteKit
SvelteKit is the tidiest of the meta-frameworks for this: the $env modules enforce the key
split at build time — importing $env/static/private into client code is a build error,
not a silently leaked secret — and the two hook files map one-to-one onto the SDK.
1. Install
npm i bugboard
BUGBOARD_KEY_ID=bbk_xxxxxxxx BUGBOARD_SIGNING_SECRET=bb_sec_xxxxxxxx PUBLIC_BUGBOARD_API_KEY=bb_pub_xxxxxxxx
2. Two client modules
// src/lib/bugboard.server.ts import { createClient } from 'bugboard'; import { BUGBOARD_KEY_ID, BUGBOARD_SIGNING_SECRET } from '$env/static/private'; import { dev } from '$app/environment'; export default createClient({ keyId: BUGBOARD_KEY_ID, signingSecret: BUGBOARD_SIGNING_SECRET, environment: dev ? 'development' : 'production', });
// src/lib/bugboard.ts (client) import { createClient } from 'bugboard'; import { PUBLIC_BUGBOARD_API_KEY } from '$env/static/public'; import { dev } from '$app/environment'; export default createClient({ apiKey: PUBLIC_BUGBOARD_API_KEY, environment: dev ? 'development' : 'production', });
The .server.ts suffix is the convention worth keeping: an accidental client import fails at
build time rather than shipping your signing secret.
3. The hooks
// src/hooks.server.ts import type { HandleServerError } from '@sveltejs/kit'; import bugboard from '$lib/bugboard.server'; export const handleError: HandleServerError = async ({ error, event, status, }) => { if (status !== 404) { bugboard.criticalHigh( `Server error: ${event.route.id ?? event.url.pathname}`, error, ['sveltekit', 'server'], ); await bugboard.flush(); // adapter-vercel / adapter-cloudflare may freeze right after } return { message: 'Internal Error' }; };
// src/hooks.client.ts import type { HandleClientError } from '@sveltejs/kit'; import bugboard from '$lib/bugboard'; export const handleClientError: HandleClientError = ({ error, event }) => { bugboard.critical( `Client error: ${event.route.id ?? event.url.pathname}`, error, ['sveltekit', 'client'], ); return { message: 'Something went wrong' }; };
Two details carry most of the value here:
event.route.idis the route pattern (/products/[id]), which is what you want in the title.event.url.pathnameis the URL and would create a card per product. See Deduplication.- The
status !== 404guard. A public site 404s constantly; those are not your bugs and they will bury the ones that are.
4. Report from anywhere else
In a load function, form action or component:
import bugboard from '$lib/bugboard.server'; export const actions = { checkout: async ({ request }) => { try { await processCheckout(await request.formData()); } catch (err) { bugboard.criticalHigh('Checkout action failed', err, ['checkout']); await bugboard.flush(); return fail(500, { error: 'Checkout failed' }); } }, };
Delivery by adapter
The await flush() in hooks.server.ts is what makes this portable across adapters:
adapter-node— the automaticbeforeExithook covers normal shutdown; add aSIGTERMhandler if you deploy to containers, as in the Node guide.adapter-vercel,adapter-cloudflare,adapter-netlify— no lifecycle hook at all; the runtime may freeze the instant you return. The explicit flush is the only thing delivering your reports. See Serverless & edge.- The browser — the SDK flushes on
pagehidewithkeepalive, so no work needed.
Next steps
- JavaScript SDK — the base guide and the full method surface.
- Vite SPA — the browser-side global handlers.
- API Reference — every config option and the HTTP contract.