Next.js
Next.js runs your code in three places — the server, the client bundle, and possibly a serverless or edge runtime — and each needs different treatment. The pattern is two client modules and one guard.
1. Install
npm i bugboard
2. Two modules, one guard
// lib/bugboard.server.ts import 'server-only'; // build error if this is ever imported from a client component import { createClient } from 'bugboard'; export default createClient({ keyId: process.env.BUGBOARD_KEY_ID, // bbk_… signingSecret: process.env.BUGBOARD_SIGNING_SECRET, // bb_sec_… environment: process.env.VERCEL_ENV ?? process.env.NODE_ENV, release: process.env.VERCEL_GIT_COMMIT_SHA?.slice(0, 7), });
// lib/bugboard.client.ts 'use client'; import { createClient } from 'bugboard'; export default createClient({ apiKey: process.env.NEXT_PUBLIC_BUGBOARD_API_KEY, // bb_pub_… environment: process.env.NEXT_PUBLIC_VERCEL_ENV, });
BUGBOARD_KEY_ID=bbk_xxxxxxxx BUGBOARD_SIGNING_SECRET=bb_sec_xxxxxxxx NEXT_PUBLIC_BUGBOARD_API_KEY=bb_pub_xxxxxxxx
The server-only package (npm i server-only) turns a leaked secret key into a build failure.
Use it — never put a secret key behind a NEXT_PUBLIC_ prefix; anything with a
client-exposed prefix ends up in the bundle, and the SDK cannot detect this for you.
3. Route handlers and server actions
Flush before returning, because these may run on a serverless or edge target where nothing else will drain the queue:
// app/api/checkout/route.ts import bugboard from '@/lib/bugboard.server'; export async function POST(request: Request) { try { return Response.json(await processCheckout(await request.json())); } catch (err) { bugboard.criticalHigh('Checkout API failed', err, ['api', 'checkout']); return Response.json({ error: 'Checkout failed' }, { status: 500 }); } finally { await bugboard.flush(); } }
flush() on an empty queue is effectively free, so an unconditional finally is the right
shape — you don't need to track whether you reported anything.
4. Error boundaries
App Router gives you two files, and you want both.
// app/error.tsx — recoverable errors within a route segment 'use client'; import { useEffect } from 'react'; import bugboard from '@/lib/bugboard.client'; export default function Error({ error, reset, }: { error: Error & { digest?: string }; reset: () => void; }) { useEffect(() => { bugboard.critical(`Route error: ${error.message}`, error, ['nextjs', 'client']); }, [error]); return ( <div> <h2>Something went wrong</h2> <button onClick={reset}>Try again</button> </div> ); }
// app/global-error.tsx — root layout errors; must render <html> and <body> 'use client'; import { useEffect } from 'react'; import bugboard from '@/lib/bugboard.client'; export default function GlobalError({ error }: { error: Error & { digest?: string } }) { useEffect(() => { bugboard.criticalHigh(`Root error: ${error.message}`, error, ['nextjs', 'fatal']); }, [error]); return ( <html> <body> <h2>Something went wrong</h2> </body> </html> ); }
In production, a server component error reaching the client is redacted to a generic message
plus a digest. So error.message from error.tsx is often unhelpful, and the useful
report is the server-side one — which is exactly what the next hook is for.
5. instrumentation.ts — the server errors you'd otherwise miss
This catches server errors Next.js handles internally, before they are redacted:
// instrumentation.ts (Next.js 15+) export async function onRequestError(err, request, context) { const bugboard = (await import('@/lib/bugboard.server')).default; bugboard.criticalHigh( `Server error: ${context.routePath ?? request.path}`, err, ['nextjs', 'server', context.routerKind], ); await bugboard.flush(); }
Use context.routePath (the pattern) over request.path (the URL), so repeated failures
deduplicate into one card instead of one per id. See
Deduplication.
Pages Router, if that's what you're on: use _error.tsx for client errors and wrap API
routes in the same try/finally as above.
6. Real file names instead of chunk names
A Next.js build minifies and chunks both halves of your app, so out of the box cards say
.next/server/chunks/456.js:1 rather than app/api/checkout/route.ts:42. Two changes fix that,
and neither uploads a source map anywhere.
Install the build plugin. It writes the real file and line into each call at build time, before chunking happens, and it covers client and server code alike.
Turbopack — the default in Next 16, and in Next 15 with --turbopack:
// next.config.js module.exports = { turbopack: { rules: { '*.ts': { loaders: ['bugboard/webpack'] }, '*.tsx': { loaders: ['bugboard/webpack'] }, }, }, };
Do not add an as field to those rules. Turbopack appends it to the filename, so as: '*.tsx'
makes it look for page.tsx.tsx and the build fails.
webpack — Next 15's default, or next build --webpack:
// next.config.js module.exports = { webpack(config) { config.module.rules.push({ test: /\.[cm]?[jt]sx?$/, exclude: /node_modules/, enforce: 'pre', // must precede SWC, while line numbers are original use: 'bugboard/webpack', }); return config; }, };
On Next 16 a webpack key with no turbopack key is a hard build error, because Turbopack is
the default. Use the Turbopack form, or pass --webpack explicitly.
Either way, cards then read app/api/checkout/route.ts:42 and app/page.tsx:18 — including
calls written inline in JSX, like onClick={() => bugboard.minorLow('…')}.
What this does not fix
file_name and line_number become exact, but the stack inside a caught error's description
stays as chunk coordinates on the Next server. That is not something the SDK can change: Next
installs its own Error.prepareStackTrace (next/dist/server/patch-error-inspect.js) to power
its error overlay, which replaces the formatter Node applies source maps in. Enabling source maps
has no effect on error.stack while that patch is installed — verified on Next 16 with
bugboard/preload, with NODE_OPTIONS=--enable-source-maps, and with
experimental.serverSourceMaps: true; all three leave the stack text unchanged.
So on Next, rely on file_name/line_number for location. bugboard/preload is still worth
adding for non-Next Node processes in the same codebase, but it will not change what Next's
server puts in a stack.
With both in place, server stacks — including the stack inside a caught error's description — name your real source files.
Delivery notes
- On Vercel, route handlers, server actions and
onRequestErrorall run in an environment that can freeze the instant you return — theawait flush()is not optional there. - On a long-running Node server (
next startin a container), the automaticbeforeExithook covers normal shutdown, but notSIGTERM— see the Node guide. - In the browser, the SDK flushes on
pagehidewithkeepalive, so client reports survive the page going away.
Next steps
- JavaScript SDK — the base guide and the full method surface.
- Serverless & edge —
waitUntiland tuning for pay-per-millisecond runtimes. - Vite SPA — the browser-side global handlers, which apply to the Next.js client bundle too.
- API Reference — every config option and the HTTP contract.