Skip to main content
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.
// 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.
// 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.
// 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.
createBoundaryLogger({
  apiKey: process.env.BOUNDARY_API_KEY,
  batch: { size: 10, maxQueueSize: 100 },  // smaller footprint
});

Server Actions

Same pattern as route handlers — flush before returning.
// 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.
// 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 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. 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:
const environment =
  process.env.VERCEL_ENV === "production" ? "production" :
  process.env.VERCEL_ENV === "preview"    ? "staging" :
                                            "development";

See also

Vercel / Lambda

Per-invocation flush pattern

Browser

Client-side contracts