Next.js App and Pages Router

Purpose and Scope

This page explains the Next.js wiring pattern for AI SDK UI when the server-side responder is a harness-backed agent rather than a one-shot language model call. The documented example uses a client component with a chat hook, a server-defined HarnessAgent, a small session store, and an API route that streams the agent turn back as UI messages. That makes the page most directly useful for App Router projects, while the same responsibilities apply to Pages Router API routes: accept chat requests, resume the correct agent session, run one turn, stream the response, and persist resume state when the turn finishes or pauses.

Sources: content/docs/03-ai-sdk-harnesses/07-ui.mdx

The key mental shift is that harness conversations are stateful. In a normal model-based route, the server can often reconstruct the conversation by replaying UI messages into a model call. In the harness flow, the harness owns conversation state, sandbox identity, interrupted progress, and approval continuations. The route should therefore treat the incoming chat id as the stable key for resuming or creating a HarnessAgentSession. UI messages are still important because they carry the user’s new turn through the transport, but they are not the sole source of truth for the conversation.

Sources: content/docs/03-ai-sdk-harnesses/07-ui.mdx

Relevant Source Files

  • content/docs/03-ai-sdk-harnesses/07-ui.mdx — Documents the first-party harness-to-UI integration: a client page using useChat, a server HarnessAgent definition, an opaque resume-state store, and a route that converts between UI message streams and harness output.

Core Primitives

The client primitive is the chat hook configured with a DefaultChatTransport. The hook owns rendered messages, the current send status, and the sendMessage function used by the form submit handler. The transport points at an application API endpoint, so the React component does not need to know which harness or provider runs on the server. It renders ordinary text parts directly and displays tool or dynamic-tool parts as structured JSON, which is useful for agentic systems where tool calls, approvals, and intermediate events may appear in the message stream.

Sources: content/docs/03-ai-sdk-harnesses/07-ui.mdx

The server primitive is HarnessAgent. In the documented setup, the agent is created with the Claude Code harness adapter and a Vercel sandbox configured for a Node runtime and an exposed port. The instructions string defines the assistant behavior for the coding assistant. This separation is important in Next.js because the client route should only call the API endpoint; sandbox creation, harness selection, and agent instructions stay in server files under the API route. Keeping those concerns server-only also avoids leaking implementation details or credentials to the browser.

Sources: content/docs/03-ai-sdk-harnesses/07-ui.mdx

App Router Execution Flow

In the App Router version, the UI lives in a client component such as app/page.tsx. It imports useChat from the React integration and DefaultChatTransport from the core package, sets a stable chat id, and points the transport at /api/chat. The form calls sendMessage with the current text and then clears local input state. The disabled state follows the hook status so the user cannot submit while the stream is not ready. Message rendering iterates over parts instead of assuming every assistant response is plain text.

Sources: content/docs/03-ai-sdk-harnesses/07-ui.mdx

On the server, app/api/chat/agent.ts defines the long-lived agent configuration, while app/api/chat/session-store.ts abstracts resume and detach behavior. The route’s job is to bridge formats. It converts incoming UI messages to model messages, resumes or creates the harness session for the chat id, runs the harness turn, and converts the resulting stream back to a UI message stream response. Afterward it detaches the session and stores only the opaque resume state. That final step is what allows the next request to continue the same sandbox-backed conversation.

Sources: content/docs/03-ai-sdk-harnesses/07-ui.mdx

'use client';
 
import { useChat } from '@ai-sdk/react';
import { DefaultChatTransport } from 'ai';
 
const { messages, sendMessage, status } = useChat({
  id: 'example-chat',
  transport: new DefaultChatTransport({ api: '/api/chat' }),
});

Pages Router Adaptation

For a Pages Router application, keep the same public contract but move the server boundary to a Pages API handler. The browser should still talk to one chat endpoint through the transport, and the handler should still resolve the chat id, resume or create the harness session, run the turn, and return a stream that the UI hook can read. The important part is not the directory name; it is preserving the conversion boundary between UI messages and the agent stream, and preserving the opaque resume state outside the rendered React tree.

Sources: content/docs/03-ai-sdk-harnesses/07-ui.mdx

Pages Router projects often have a different request and response object shape than App Router route handlers, so isolate framework-specific HTTP details at the edge of the handler. The session store, agent definition, and message conversion logic should remain reusable server modules. That makes it easier to migrate from Pages Router to App Router later, or to share the same harness-backed chat behavior across multiple endpoints. Avoid adding client-side logic that attempts to replay or reconstruct harness state; the source documentation explicitly assigns resume responsibility to the route and session store.

Sources: content/docs/03-ai-sdk-harnesses/07-ui.mdx

Session Management and Production Storage

The session store example uses a simple in-memory record keyed by chat id, but it also states that production applications should use durable storage. The stored value is not a transcript or a manually edited continuation object. It is the opaque resume state returned by session.detach. If a turn paused for approval or was interrupted, that resume state carries the continuation data internally. This design lets the application preserve human-in-the-loop or interrupted agent progress without making the UI responsible for serializing every internal harness event.

Sources: content/docs/03-ai-sdk-harnesses/07-ui.mdx

A practical production implementation should choose a chat id that is stable across page reloads and server processes, then use that id as the lookup key for resume state. The same id can also be passed as the harness sessionId so the sandbox receives a stable identity. When requests may run concurrently, treat detach-and-persist as part of the request lifecycle and ensure the newest valid resume state wins. When a user starts a new conversation, use a new chat id rather than clearing only the visible message list.

Sources: content/docs/03-ai-sdk-harnesses/07-ui.mdx

API Components Reference

ComponentWhere it appearsRole
useChatClient pageMaintains UI messages, sendMessage, and status for the chat experience.
DefaultChatTransportClient pageSends chat requests to the configured API path.
HarnessAgentServer agent moduleDefines the harness, sandbox, and instructions used for agent turns.
createSessionSession factory contractCreates or resumes a HarnessAgentSession with sessionId and optional resumeFrom state.
session.detachSession lifecycleProduces the opaque resume state that should be persisted after a turn.
convertToModelMessagesRoute boundaryConverts UI messages before running the harness turn.
createUIMessageStreamResponseRoute boundaryReturns the harness result as a UI-compatible streaming response.

Next Steps

After the basic chat route is working, extend the message renderer before extending the server loop. Agentic chats can emit text, tool parts, dynamic-tool parts, approval-related events, and metadata, so the UI should branch by part type rather than concatenate assistant text blindly. Then replace the sample in-memory session store with durable storage and decide how chat ids are created, authenticated, and cleaned up. For agent systems that require approvals, read the tool approval and human-in-the-loop material next, because approval pauses depend on the same resume-state discipline described here.

Sources: content/docs/03-ai-sdk-harnesses/07-ui.mdx