Add an AI Agent to a Next.js App
This tutorial shows how to add a streaming AI agent to a Next.js app using TypeScript and the App Router. You’ll build the agent on the server, expose it through a Route Handler, and render tokens as they arrive in a client component — the same pattern you’d reach for to stream an AI agent in Next.js without writing any SSE plumbing by hand.
The shape of a Next.js AI agent with Reactive Agents is two pieces:
- Server — a Route Handler builds the agent and returns
AgentStream.toSSE(agent.runStream(prompt)), a standard Web APIResponsecarrying a Server-Sent Events (SSE) body. - Client — a
'use client'component calls theuseAgentStreamhook, which consumes that SSE stream and exposes reactivetext,status, anderrorstate.
SSE is the bridge: the server agent reasons and calls tools, and each token streams to the browser as it’s produced.
Prerequisites
Section titled “Prerequisites”- A Next.js 13+ project using the App Router (
app/directory) - Node.js 18+
- An API key for a model provider (this guide uses Anthropic)
Step 1 — Install
Section titled “Step 1 — Install”bun add reactive-agents @reactive-agents/reactUsing npm, pnpm, or yarn instead:
npm install reactive-agents @reactive-agents/reactreactive-agents is the framework you run on the server. @reactive-agents/react provides the client hooks.
Step 2 — The server Route Handler
Section titled “Step 2 — The server Route Handler”Create a Route Handler at app/api/agent/route.ts. It builds an agent and returns the SSE Response directly — Next.js streams it to the browser.
import { ReactiveAgents, AgentStream } from "reactive-agents";
// The agent framework uses Node.js APIs — run this route on the Node runtime,// not the Edge runtime. (Node is the App Router default; this makes it explicit.)export const runtime = "nodejs";
export async function POST(req: Request) { const { prompt } = await req.json();
const agent = await ReactiveAgents.create() .withProvider("anthropic") .withModel("claude-sonnet-4-6") .withReasoning() .withTools() .build();
// toSSE() returns a standard Web API Response with a Server-Sent Events body. return AgentStream.toSSE(agent.runStream(prompt));}What each call does:
.withProvider("anthropic")— picks the model provider. Swap in"openai","google","ollama", etc..withModel("claude-sonnet-4-6")— selects the model..withReasoning()— enables the reasoning loop so the agent can plan across multiple steps..withTools()— enables the built-in tools (file, fetch, shell, and friends) so the agent can take actions, not just talk.agent.runStream(prompt)— runs the agent and yields a stream of events (TextDelta,IterationProgress,StreamCompleted, …).AgentStream.toSSE(...)— adapts that stream into an SSEResponse. No manualReadableStreamwiring needed.
Step 3 — The client component
Section titled “Step 3 — The client component”Create a client component that calls useAgentStream("/api/agent") and renders the streaming text. The 'use client' directive is required because the hook uses React state and fetch.
"use client";
import { useState } from "react";import { useAgentStream } from "@reactive-agents/react";
export function AgentChat() { const [prompt, setPrompt] = useState(""); const { text, status, error, run, cancel } = useAgentStream("/api/agent");
return ( <div> <form onSubmit={(e) => { e.preventDefault(); run(prompt); }} > <input value={prompt} onChange={(e) => setPrompt(e.target.value)} placeholder="Ask the agent anything..." /> <button type="submit" disabled={status === "streaming"}> {status === "streaming" ? "Thinking..." : "Ask"} </button> {status === "streaming" && ( <button type="button" onClick={cancel}> Stop </button> )} </form>
{/* Tokens accumulate in `text` as they stream from the server */} <p style={{ whiteSpace: "pre-wrap" }}>{text}</p>
{status === "error" && <p style={{ color: "red" }}>{error}</p>} </div> );}Drop it into a page:
import { AgentChat } from "./agent-chat";
export default function Home() { return <AgentChat />;}That’s the full loop. Click Ask and the agent’s reasoning streams into the page token by token.
What useAgentStream returns
Section titled “What useAgentStream returns”useAgentStream(endpoint, requestInit?) returns:
| Property | Type | Description |
|---|---|---|
text | string | Accumulated output, growing as tokens arrive |
status | "idle" | "streaming" | "completed" | "error" | Current execution state |
output | string | null | Full output once status === "completed" |
events | AgentStreamEvent[] | All raw events received since the last run() |
error | string | null | Error message when status === "error" |
run | (prompt: string, body?) => void | Start a stream; cancels any active one |
cancel | () => void | Cancel the active stream |
Pass extra fields to the server via the second run argument — they’re merged into the request body:
run("Summarize this thread", { sessionId, temperature: 0.3 });Then read them in the Route Handler: const { prompt, sessionId, temperature } = await req.json();.
Need a one-shot call instead of streaming? Use useAgent("/api/agent"), which returns { output, loading, error, run } and resolves on completion. It expects the endpoint to return JSON ({ output: "..." }) rather than an SSE stream.
Step 4 — Production notes
Section titled “Step 4 — Production notes”- Keep API keys on the server. The Route Handler runs server-side, so your provider key (e.g.
ANTHROPIC_API_KEY) stays in server-only environment variables. Never expose it to the client or prefix it withNEXT_PUBLIC_. - Pin the Node runtime. As noted above, set
export const runtime = "nodejs"on the agent route. - Cancellation.
useAgentStreamaborts the in-flightfetchwhen you callcancel()or start a newrun(), so abandoned requests don’t keep streaming.
Stability note
Section titled “Stability note”@reactive-agents/react is currently experimental. The hooks work, but the SSE event contract between the server adapter and the client hooks may change in a future minor release. Pin your versions and check the changelog before upgrading if you depend on the raw events shape. The server-side AgentStream.toSSE adapter and the core framework are stable.
Next steps
Section titled “Next steps”- Web Framework Integration — the same pattern for Vue and Svelte, plus iteration-progress bars and typed events.
- Quickstart — build and run your first agent from scratch.