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
npm i bugboard
2. Configure runtimeConfig
// 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 }, }, });
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:
// 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
// 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
<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:
// 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.
Next steps
- JavaScript SDK — the base guide and the full method surface.
- Serverless & edge — if you deploy Nitro to Vercel, Cloudflare or Deno.
- API Reference — every config option and the HTTP contract.