> ## Documentation Index
> Fetch the complete documentation index at: https://docs.withboundary.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Next.js

> App Router route handlers, server components, and the Edge runtime

Next.js has three runtimes: Node (default for route handlers, server actions, server components), Edge (opt-in via `runtime = "edge"`), and the browser. The SDK works in all three, but the wiring differs.

## Where to initialize the logger

Put it in a single module and re-export. This avoids creating multiple loggers per request under the file-scoped import caching that Next does.

```typescript theme={null}
// lib/boundary.ts
import { createBoundaryLogger } from "@withboundary/sdk";
import { defineContract } from "@withboundary/contract";

export const logger = createBoundaryLogger({
  apiKey: process.env.BOUNDARY_API_KEY,
  environment: process.env.VERCEL_ENV === "production" ? "production" : "staging",
});

export const leadContract = defineContract({
  name: "lead-scoring",
  schema,
  rules,
  logger,
});
```

## App Router — Route Handlers (Node runtime)

Flush per request. Next's serverless Node invocations freeze quickly after the response is returned — `beforeExit` is not guaranteed to fire.

```typescript theme={null}
// app/api/score/route.ts
import { leadContract, logger } from "@/lib/boundary";

export async function POST(request: Request) {
  try {
    const result = await leadContract.accept((attempt) => runLLM(attempt));
    if (!result.ok) {
      return Response.json({ error: result.error.message }, { status: 422 });
    }
    return Response.json(result.data);
  } finally {
    await logger?.flush(1000);
  }
}
```

## App Router — Route Handlers (Edge runtime)

On the Edge runtime, `process.beforeExit` does not exist. `ctx.waitUntil` is not exposed directly in App Router handlers — flush explicitly.

```typescript theme={null}
// app/api/score/route.ts
export const runtime = "edge";

import { leadContract, logger } from "@/lib/boundary";

export async function POST(request: Request) {
  const result = await leadContract.accept((attempt) => runLLM(attempt));
  await logger?.flush(1000);  // awaited before response returns
  return result.ok
    ? Response.json(result.data)
    : Response.json({ error: result.error.message }, { status: 422 });
}
```

On Edge, tighten the capture policy — there's no filesystem, no long-running process, and memory is bounded tighter than on Node.

```typescript theme={null}
createBoundaryLogger({
  apiKey: process.env.BOUNDARY_API_KEY,
  batch: { size: 10, maxQueueSize: 100 },  // smaller footprint
});
```

## Server Actions

Same pattern as route handlers — flush before returning.

```typescript theme={null}
// app/actions.ts
"use server";
import { leadContract, logger } from "@/lib/boundary";

export async function scoreLead(input: string) {
  try {
    const result = await leadContract.accept((attempt) => runLLM(attempt, input));
    return result;
  } finally {
    await logger?.flush(1000);
  }
}
```

## React Server Components

Server components run once per request on the server. Same flushing rules apply — if your RSC invokes a contract, flush at the end.

```typescript theme={null}
// app/leads/[id]/page.tsx
import { leadContract, logger } from "@/lib/boundary";

export default async function LeadPage({ params }: { params: { id: string } }) {
  try {
    const result = await leadContract.accept((attempt) => runLLM(attempt));
    return <LeadView data={result.ok ? result.data : null} />;
  } finally {
    await logger?.flush(1000);
  }
}
```

## Middleware

Next middleware runs on the Edge runtime. Don't wire a logger here — middleware is for routing / headers / auth. If you somehow need to observe middleware decisions, use a custom [`write`](/sdk/custom-sinks) sink that posts to your own endpoint instead of Boundary directly.

## Client components

Browser-side contracts are legitimate (e.g. validating an LLM response streamed directly to the client). Use the [browser platform guide](/sdk/platforms/browser). Never pass an API key into the client bundle — `NEXT_PUBLIC_*` env vars are exposed to users. Either:

* Wire the logger server-side only, or
* Use a custom `write` that POSTs to your own authenticated server route.

## Environment detection

Useful snippet for `environment`:

```typescript theme={null}
const environment =
  process.env.VERCEL_ENV === "production" ? "production" :
  process.env.VERCEL_ENV === "preview"    ? "staging" :
                                            "development";
```

## See also

<CardGroup cols={2}>
  <Card title="Vercel / Lambda" icon="aws" href="/sdk/platforms/vercel-aws-lambda">
    Per-invocation flush pattern
  </Card>

  <Card title="Browser" icon="browser" href="/sdk/platforms/browser">
    Client-side contracts
  </Card>
</CardGroup>
