# Nuxt

Nuxt's `runtimeConfig` already splits public from private for you, which maps cleanly onto the
two key types: `public` reaches the browser and takes the **publishable** key, everything else
stays on the server and takes the **secret** key.

## 1. Install

```bash
npm i bugboard
```

## 2. Configure `runtimeConfig`

```ts
// nuxt.config.ts
export default defineNuxtConfig({
    runtimeConfig: {
        bugboardKeyId: '', // NUXT_BUGBOARD_KEY_ID — server only
        bugboardSigningSecret: '', // NUXT_BUGBOARD_SIGNING_SECRET — server only
        public: {
            bugboardApiKey: '', // NUXT_PUBLIC_BUGBOARD_API_KEY — client
        },
    },
});
```

```dotenv
NUXT_BUGBOARD_KEY_ID=bbk_xxxxxxxx
NUXT_BUGBOARD_SIGNING_SECRET=bb_sec_xxxxxxxx
NUXT_PUBLIC_BUGBOARD_API_KEY=bb_pub_xxxxxxxx
```

Never put the signing secret under `public` — anything there is serialized into the page.

## 3. The client plugin

This also wires up Vue's error handler:

```ts
// plugins/bugboard.client.ts
import { createClient } from 'bugboard';

export default defineNuxtPlugin((nuxtApp) => {
    const config = useRuntimeConfig();

    const bugboard = createClient({
        apiKey: config.public.bugboardApiKey,
        environment: import.meta.dev ? 'development' : 'production',
    });

    nuxtApp.hook('vue:error', (error, instance, info) => {
        bugboard.critical(`Vue error: ${info}`, error, ['nuxt', 'client']);
    });

    return { provide: { bugboard } };
});
```

Using `info` — Vue's lifecycle hook name, a small fixed set — in the title rather than the raw
error message keeps cards well grouped instead of producing one per message variant.

## 4. The server plugin

```ts
// server/plugins/bugboard.ts
import { createClient } from 'bugboard';

export default defineNitroPlugin((nitroApp) => {
    const config = useRuntimeConfig();

    const bugboard = createClient({
        keyId: config.bugboardKeyId,
        signingSecret: config.bugboardSigningSecret,
        environment: process.env.NODE_ENV,
    });

    nitroApp.hooks.hook('error', async (error, { event }) => {
        bugboard.criticalHigh(
            `Server error: ${event?.path ?? 'unknown'}`,
            error,
            ['nuxt', 'server'],
        );
        await bugboard.flush(); // Nitro may be deployed to a serverless target
    });
});
```

The `await flush()` here is deliberate: Nitro targets Node, Vercel, Cloudflare and Deno from
the same code, and on the serverless targets nothing else will drain the queue. On a Node
target the flush is nearly free, so leaving it in costs you nothing.

## 5. Report from components

```vue
<script setup lang="ts">
const { $bugboard } = useNuxtApp();

async function submit() {
    try {
        await $fetch('/api/checkout', { method: 'POST' });
    } catch (err) {
        $bugboard.major('Checkout request failed', err, ['checkout']);
    }
}
</script>
```

## Server routes

In a server route or API handler, resolve the server client and flush in a `finally`:

```ts
// server/api/checkout.post.ts
export default defineEventHandler(async (event) => {
    try {
        return await processCheckout(await readBody(event));
    } catch (err) {
        bugboard.criticalHigh(`Checkout API failed: ${event.path}`, err, [
            'api',
        ]);
        throw createError({
            statusCode: 500,
            statusMessage: 'Checkout failed',
        });
    } finally {
        await bugboard.flush();
    }
});
```

Keep the title stable — `event.path` on a route with a parameter (`/api/orders/91847`) creates
a card per order. Prefer a fixed string, or the route pattern, and let the description carry
the specifics. See
**[Deduplication](/docs/api-reference#deduplication-important-for-sdk-design)**.

## Next steps

- **[JavaScript SDK](/docs/sdks/javascript)** — the base guide and the full method surface.
- **[Serverless & edge](/docs/frameworks/serverless)** — if you deploy Nitro to Vercel,
  Cloudflare or Deno.
- **[API Reference](/docs/api-reference)** — every config option and the HTTP contract.
