# 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

```bash
npm i bugboard
```

## 2. Two modules, one guard

```ts
// 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),
});
```

```ts
// 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,
});
```

```dotenv
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:

```ts
// 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.

```ts
// 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>
    );
}
```

```ts
// 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:

```ts
// 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](/docs/api-reference#deduplication-important-for-sdk-design)**.

**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 `onRequestError` all run in an environment
  that can freeze the instant you return — the `await flush()` is not optional there.
- **On a long-running Node server** (`next start` in a container), the automatic `beforeExit`
  hook covers normal shutdown, but not `SIGTERM` — see
  **[the Node guide](/docs/frameworks/node#delivery-and-flushing)**.
- **In the browser**, the SDK flushes on `pagehide` with `keepalive`, so client reports survive
  the page going away.

## Next steps

- **[JavaScript SDK](/docs/sdks/javascript)** — the base guide and the full method surface.
- **[Serverless & edge](/docs/frameworks/serverless)** — `waitUntil` and tuning for
  pay-per-millisecond runtimes.
- **[Vite SPA](/docs/frameworks/vite-spa)** — the browser-side global handlers, which apply to
  the Next.js client bundle too.
- **[API Reference](/docs/api-reference)** — every config option and the HTTP contract.
