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

# Tracing AI SDK in Next.js with Tracevell

## Overview

We'll explore a simple emotional support chat application built with Next.js that:

1. Provides a chat interface for users seeking emotional support
2. Uses OpenAI's `gpt-5.6-luna` to generate empathetic responses
3. Traces the entire process with Tracevell
4. Uses Vercel's AI SDK for AI interactions

This guide targets **AI SDK v7 and later**, where Tracevell registers once and traces every call with no per-call configuration. If you're still on AI SDK v5 or v6, follow the v5/v6 tab on the [Vercel AI SDK integration page](https://tracevell.com/docs/integrations/vercel-ai-sdk.md) instead.

## Setup

You can use the example app from our GitHub [repo](https://github.com/lmnr-ai/lmnr-ts/tree/main/examples/nextjs-aisdk).

Alternatively, for a clean install, follow these steps:

**1. Initialize a Next.js app**

```bash theme={null}
npx create-next-app@latest
```

Learn more in the [Next.js docs](https://nextjs.org/docs/app/getting-started/installation).

**2. Install Dependencies**

Let's now install the required packages:

```bash theme={null}
npm install ai@latest @ai-sdk/openai@latest @lmnr-ai/lmnr@latest
```

**3. Environment Setup**

```bash theme={null}
cp .env.local.example .env.local
```

And then fill in the `.env.local` file:

```bash .env.local theme={null}
LMNR_PROJECT_API_KEY=your-tracevell-project-api-key
OPENAI_API_KEY=your-openai-api-key
```

To get the project API key, go to the Tracevell dashboard, click the project settings,
and generate a project API key. This is available both in the cloud and in the self-hosted version of Tracevell.

Specify the key at `Tracevell` initialization. If not specified,
Tracevell will look for the key in the `LMNR_PROJECT_API_KEY` environment variable.

Get your [OpenAI API key](https://platform.openai.com/api-keys) from the OpenAI dashboard.

**4. Update next.config.ts**

Add the following to your `next.config.ts` file:

```javascript next.config.ts theme={null}
const nextConfig = {
  serverExternalPackages: ['@lmnr-ai/lmnr'],
};

export default nextConfig;
```

This is because Tracevell depends on OpenTelemetry, which uses some Node.js-specific functionality, and we need to inform Next.js about it. Learn more in the [Next.js docs](https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages).

**5. Project Structure**

The project has the following structure:

```
nextjs-app/
├── .env.local
├── app/
│   ├── api/
│   │   └── chat/
│   │       └── route.ts
│   ├── page.tsx
│   └── layout.tsx
├── components/
│   └── chat-ui.tsx
├── instrumentation.ts
└── ...
```

## Implementation

Let's look at the key components of our Next.js application with Tracevell tracing:

### 1. instrumentation.ts

This file is crucial as it registers Tracevell with the AI SDK.
Next.js automatically loads this file during initialization.

```typescript instrumentation.ts theme={null}
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    const { registerTelemetry } = await import('ai')
    const { LaminarAiSdkTelemetry } = await import('@lmnr-ai/lmnr')

    registerTelemetry(new LaminarAiSdkTelemetry({
      laminarOptions: {
        projectApiKey: process.env.LMNR_PROJECT_API_KEY,
      },
    }))
  }
}
```

`LaminarAiSdkTelemetry` is the recommended way to trace the AI SDK on v7 and later. You register it once, and every `generateText`, `streamText`, `generateObject`, `streamObject`, and `embed` call in your app is traced: model turns, tool calls, token usage, and cost. There is no per-call configuration to add or forget.

The constructor calls `Laminar.initialize()` for you when Tracevell isn't already initialized, so you don't need a separate init call. `laminarOptions` takes everything `Laminar.initialize()` accepts. Omit it entirely and Tracevell reads `LMNR_PROJECT_API_KEY` from the environment.

Tracevell only works in the 'nodejs' runtime of Next.js.

Learn more about the `instrumentation.ts` file in the [Next.js docs](https://nextjs.org/docs/app/guides/instrumentation).

> [!NOTE]
>
> Tracevell must be registered at the entry point of the application, before the first AI SDK call.
> For Next.js, the `instrumentation.ts` file is ideal for this purpose as it's loaded early in the application lifecycle.
>
> If you already register a tracer provider with `registerOTel` from `@vercel/otel`, don't let Tracevell register a second one: see [Coexisting with @vercel/otel](https://tracevell.com/docs/integrations/vercel-ai-sdk.md#coexisting-with-@vercel/otel).

### 2. app/page.tsx

This file contains the main page layout for our chat application:

```tsx app/page.tsx theme={null}
import ChatUI from "@/components/chat-ui";

export default function Home() {
  return (
    <div className="grid grid-rows-[auto_1fr_auto] min-h-screen p-4 sm:p-6 font-[family-name:var(--font-geist-sans)]">
      <header className="py-4 text-center">
        <h1 className="text-2xl font-bold text-blue-600 mb-1">Therapy Chat</h1>
        <p className="text-sm text-gray-600 dark:text-gray-400 max-w-lg mx-auto">
          A safe space to share your thoughts and receive supportive guidance.
          Your conversation is private and confidential.
        </p>
      </header>
      <main className="w-full max-w-4xl mx-auto my-4">
        <ChatUI />
      </main>
      <footer className="py-4 text-center text-sm text-gray-500 dark:text-gray-400">
        <p>AI-powered support | Not a substitute for professional medical advice</p>
      </footer>
    </div>
  );
}
```

### 3. app/api/chat/route.ts

This file contains the API route handler that processes chat messages and communicates with the OpenAI API.

There is no Tracevell-specific code here. Because `LaminarAiSdkTelemetry` is registered in `instrumentation.ts`, the `generateText` call below is traced as-is.

```typescript app/api/chat/route.ts theme={null}
import { openai } from "@ai-sdk/openai";
import { generateText } from "ai";
import { NextRequest, NextResponse } from "next/server";

// Therapeutic instructions for the model
const instructions = `You are an AI-powered therapist assistant. Respond with
empathy, understanding, and professionalism. Your goal is to provide supportive
responses that help the user process their feelings and thoughts. Never give
medical advice or diagnose conditions.`;

export async function POST(request: NextRequest) {
  try {
    const body = await request.json();
    const { messages } = body;

    const response = await generateText({
      model: openai("gpt-5.6-luna"),
      instructions,
      messages,
    });

    return NextResponse.json({ message: response.text });
  } catch (error) {
    console.error("Error in chat API:", error);
    return NextResponse.json(
      { error: "Failed to process request" },
      { status: 500 }
    );
  }
}
```

> [!NOTE]
>
> AI SDK v7 rejects `system` role entries inside `messages` with `AI_InvalidPromptError`. Pass the system prompt as the `instructions` option instead, as above. Tracevell still records it on the span input, so the transcript view shows the full conversation.

To turn tracing off for one call site, or to name a span, pass a `telemetry` block on that call:

```typescript theme={null}
const response = await generateText({
  model: openai("gpt-5.6-luna"),
  instructions,
  messages,
  telemetry: { functionId: "therapy-chat" },
});
```

### 4. components/chat-ui.tsx

This component handles the chat interface and manages the chat state.

Feel free to modify the UI as you see fit, this is just an example.

```tsx components/chat-ui.tsx [expandable] theme={null}
"use client";

import { useState } from "react";

type Message = {
  role: "user" | "assistant";
  content: string;
};

export default function ChatUI() {
  const [messages, setMessages] = useState<Message[]>([]);
  const [input, setInput] = useState("");
  const [isLoading, setIsLoading] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!input.trim()) return;

    // Add user message to chat
    const userMessage: Message = { role: "user", content: input.trim() };
    setMessages((prev) => [...prev, userMessage]);
    setInput("");
    setIsLoading(true);

    try {
      // Send message to API
      const response = await fetch("/api/chat", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          messages: [...messages, userMessage],
        }),
      });

      if (!response.ok) {
        throw new Error("Failed to get response");
      }

      const data = await response.json();
      
      // Add assistant response to chat
      setMessages((prev) => [
        ...prev,
        { role: "assistant", content: data.message },
      ]);
    } catch (error) {
      console.error("Error:", error);
      // Add error message
      setMessages((prev) => [
        ...prev,
        {
          role: "assistant",
          content: "Sorry, I couldn't process the request. Please try again.",
        },
      ]);
    } finally {
      setIsLoading(false);
    }
  };

  return (
    <div className={"flex flex-col h-[70vh] border rounded-lg " +
      "overflow-hidden bg-white dark:bg-gray-800"}>
      <div className="flex-1 overflow-y-auto p-4 space-y-4">
        {messages.length === 0 ? (
          <div className="text-center text-gray-500 dark:text-gray-400 mt-20">
            <p className="text-lg font-medium mb-2">Welcome to Therapy Chat</p>
            <p className="max-w-md mx-auto">
              Share what's on your mind, and I'll do my best to provide
              supportive guidance. Your privacy is important - this conversation
              stays between us.
            </p>
          </div>
        ) : (
          messages.map((msg, index) => (
            <div
              key={index}
              className={`flex ${
                msg.role === "user" ? "justify-end" : "justify-start"
              }`}
            >
              <div
                className={`max-w-[80%] rounded-lg px-4 py-2 ${
                  msg.role === "user"
                    ? "bg-blue-500 text-white"
                    : "bg-gray-100 dark:bg-gray-700 text-gray-800 " +
                      "dark:text-gray-200"
                }`}
              >
                {msg.content}
              </div>
            </div>
          ))
        )}
        {isLoading && (
          <div className="flex justify-start">
            <div className={"max-w-[80%] rounded-lg px-4 py-2 " +
              "bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200"}>
              <div className="flex space-x-2 items-center">
                <div className="w-2 h-2 rounded-full bg-gray-400 animate-pulse">
                </div>
                <div className={"w-2 h-2 rounded-full bg-gray-400 " +
                "animate-pulse delay-150"}></div>
                <div className={"w-2 h-2 rounded-full bg-gray-400 " + 
                "animate-pulse delay-300"}></div>
              </div>
            </div>
          </div>
        )}
      </div>
      <form
        onSubmit={handleSubmit}
        className="border-t dark:border-gray-700 p-4 flex space-x-2"
      >
        <input
          type="text"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Type your message..."
          className={"flex-1 border rounded-md px-3 py-2 focus:outline-none " +
            "focus:ring-2 focus:ring-blue-500 dark:bg-gray-700 " +
            "dark:border-gray-600 dark:text-white"}
          disabled={isLoading}
        />
        <button
          type="submit"
          disabled={isLoading || !input.trim()}
          className={"bg-blue-500 text-white rounded-md px-4 py-2 " +
            "hover:bg-blue-600 focus:outline-none focus:ring-2 " +
            "focus:ring-blue-500 disabled:opacity-50 " + 
            "disabled:cursor-not-allowed"}
        >
          Send
        </button>
      </form>
    </div>
  );
}
```

## Running the Application

Start the Next.js development server:

```bash theme={null}
npm run dev
```

## Testing the Application

1. Navigate to `http://localhost:3000` in your browser
2. Interact with the chat interface by typing messages

## Viewing Traces

After interacting with the chat, you can view the traces in your Tracevell dashboard at [https://tracevell.com](https://tracevell.com). The trace will show:

1. An `ai.generateText` span for the AI SDK operation
2. A nested `ai.llm` span for the model call, with the prompt, the response, and the model id
3. Token usage, latency, and cost

Tracevell renders this as a [transcript](https://tracevell.com/docs/platform/viewing-traces.md), so you read the conversation rather than a tree of span names.

## Key Features Demonstrated

1. **Next.js Instrumentation**: Using Next.js's instrumentation API to register Tracevell
2. **AI SDK Integration**: One registration traces every AI SDK call, with no per-call config
3. **OpenAI Tracing**: Automatic tracing of OpenAI API calls
4. **Token Usage**: Automatic calculation of tokens used for each OpenAI call
5. **Cost Estimation**: Automatic estimation of the cost of each OpenAI call

## Example Traces

![Next.js Example Traces](https://mintcdn.com/laminarai/RPYdoMdfsv6y-QUL/images/tutorials/nextjs-guide-example-trace.png?fit=max\&auto=format\&n=RPYdoMdfsv6y-QUL\&q=85\&s=2f0cd0a1b774438b01cb26c7e7d4606a)

A screenshot of a trace from the example app.

## Troubleshooting

If you encounter issues:

* Check that your API keys are correctly set in the `.env.local` file
* Verify that `registerTelemetry(new LaminarAiSdkTelemetry())` runs in the `instrumentation.ts` file, guarded by the `NEXT_RUNTIME === 'nodejs'` check
* Verify that the `next.config.ts` file is correctly configured
* Confirm you are on `ai` v7 or later (`npm ls ai`). On v5 and v6, `registerTelemetry` does not exist and you need the [`experimental_telemetry` setup](https://tracevell.com/docs/integrations/vercel-ai-sdk) instead
* Ensure all dependencies are installed
* Review the Next.js logs for any application errors


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.