URL: https://wattfare.com/docs/guides/nextjs
# Next.js integration

> A complete Next.js App Router integration: a shared server client, a session route, a streaming chat route, the provider, and a chat UI that tracks budget. Copy it wholesale.

> **Prerequisites**
>
> A Next.js 14+ app with App Router and some auth that yields a stable user id (NextAuth, Clerk, Better Auth, your own — anything). And a Wattfare app from the [dashboard](https://wattfare.com/dashboard).

## 1 · Environment variables

The secret stays server-side; only the publishable key gets the `NEXT_PUBLIC_` prefix.

```bash
WATTFARE_SECRET_KEY=sk_live_myapp_abc123...
NEXT_PUBLIC_WATTFARE_KEY=pk_live_myapp
```

## 2 · A shared server client

Construct `Wattfare` once and import it into your route handlers.

```ts
// lib/wattfare.ts
import { Wattfare } from "wattfare/server";

// One instance, reused across route handlers.
export const wf = new Wattfare({ secretKey: process.env.WATTFARE_SECRET_KEY! });
```

## 3 · The session route

One line with `sessionHandler`. It resolves the signed-in user and mints a token, or answers `401` when there's no session.

```ts
// app/api/wattfare-token/route.ts
import { wf } from "@/lib/wattfare";
import { auth } from "@/lib/auth"; // your auth

export const POST = wf.sessionHandler(async () => {
  const session = await auth();
  return session?.user?.id ?? null; // null -> 401
});
```

## 4 · The streaming chat route

Standard AI SDK `streamText` — the only Wattfare-specific lines are `wf.user(id)` and the error guards.

```ts
// app/api/chat/route.ts
import { streamText } from "ai";
import { isNotConnected, isBudgetExceeded } from "wattfare/server";
import { wf } from "@/lib/wattfare";
import { auth } from "@/lib/auth";

export async function POST(req: Request) {
  const session = await auth();
  if (!session?.user?.id) return new Response("Unauthorized", { status: 401 });

  const { messages } = await req.json();
  const ai = wf.user(session.user.id);

  try {
    const result = streamText({
      model: ai.model("anthropic/claude-sonnet-4"),
      messages,
    });
    return result.toTextStreamResponse();
  } catch (err) {
    if (isNotConnected(err))   return Response.json({ error: "not_connected" },   { status: 402 });
    if (isBudgetExceeded(err)) return Response.json({ error: "budget_exceeded" }, { status: 402 });
    throw err;
  }
}
```

## 5 · The provider

The provider is a client component, so put it behind a `"use client"` boundary and render it in your root layout.

```tsx
// app/wattfare-provider.tsx
"use client";

import { WattfareProvider } from "wattfare/react";

export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <WattfareProvider
      publishableKey={process.env.NEXT_PUBLIC_WATTFARE_KEY!}
      session="/api/wattfare-token"
    >
      {children}
    </WattfareProvider>
  );
}
```

```tsx
// app/layout.tsx
import { Providers } from "./wattfare-provider";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
```

## 6 · The chat UI

Read connection state from `useWattfare()`, gate the UI on `connected`, and refresh budget after each reply.

```tsx
// app/chat.tsx
"use client";

import { useWattfare } from "wattfare/react";
import { useChat } from "ai/react"; // optional — any chat UI works

export function Chat() {
  const { connected, connect, remainingUsd, loading, refresh } = useWattfare();
  const { messages, input, handleInputChange, handleSubmit } = useChat({
    api: "/api/chat",
    onFinish: () => refresh(), // keep budget in sync after each reply
  });

  if (loading) return <p>Loading…</p>;
  if (!connected) return <button onClick={connect}>⚡ Connect AI budget</button>;

  return (
    <div>
      <header>Budget: {remainingUsd === null ? "∞" : `$${remainingUsd.toFixed(2)}`}</header>
      {messages.map((m) => <p key={m.id}><b>{m.role}:</b> {m.content}</p>)}
      <form onSubmit={handleSubmit}>
        <input value={input} onChange={handleInputChange} placeholder="Ask anything…" />
      </form>
    </div>
  );
}
```

> **That's the whole integration**
>
> Two route handlers, one provider, one component. Swap the model id to change models; swap `auth()` for your provider. Everything else is plain Next.js + AI SDK.

## Keep going

- [Error recovery](https://wattfare.com/docs/guides/error-recovery) — Make the 402 / 429 branches above into real, friendly UI states.
- [Budget UI patterns](https://wattfare.com/docs/guides/budget-ui) — A polished budget bar and depletion warnings to drop into the header.
