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.
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.