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.
Real file names instead of chunk names
Nuxt bundles both halves of your app, so by default cards name a chunk rather than the component or handler you wrote. Two changes fix it, and neither uploads a source map.
The build plugin — register it twice. Nuxt builds your app with Vite, but Nitro builds
server/ with its own Rollup pipeline, which never sees vite.plugins. Miss the second
registration and every server route still reports a chunk:
// nuxt.config.ts import bugboard from 'bugboard/vite'; export default defineNuxtConfig({ vite: { plugins: [bugboard()] }, nitro: { rollupConfig: { plugins: [bugboard()] } }, });
This covers .vue components too — the plugin rewrites their <script> blocks before the Vue
compiler runs, which is the only point at which the file still has the line numbers you wrote. A
call placed directly in markup (@click="() => bugboard.minor('…')") is left alone, because by
then its line numbers belong to the compiler's output; move it into <script> for an exact
location.
Nitro's server stacks — Node's own source-map support. Add the preload import at the top of your server plugin, above every other import, and make sure Nitro emits server maps:
// server/plugins/bugboard.ts — line 1 import 'bugboard/preload';
// nuxt.config.ts export default defineNuxtConfig({ nitro: { sourceMap: true, }, });
Node only maps modules compiled after support is switched on, which is why the import has to come first. Full detail in the JavaScript SDK guide.
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.