BugBoardDocs

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.id is the route pattern (/products/[id]), which is what you want in the title. event.url.pathname is the URL and would create a card per product. See Deduplication.
  • The status !== 404 guard. 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 automatic beforeExit hook covers normal shutdown; add a SIGTERM handler 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 pagehide with keepalive, so no work needed.

Next steps