### Shell
```
npm install @ably/ai-transport ably ai @ai-sdk/react @ai-sdk/anthropic next react react-dom
```
## Set up authentication
Create an auth endpoint at `/api/auth` that returns an Ably JWT to the client. The endpoint validates the user and signs a token with their client ID and the channel capabilities they need. See [authentication](https://ably.com/docs/ai-transport/concepts/authentication.md?source=llms.txt) for the full setup, including how to scope capabilities to a single conversation channel.
The client below uses `authUrl: '/auth'` to fetch tokens from this endpoint.
## Create the server route
Create `app/api/chat/route.ts`. The server publishes the user's message to an Ably channel, invokes the LLM, and streams tokens into the channel. The HTTP response returns immediately; tokens flow through the durable session.
### Javascript
```
import { after } from 'next/server';
import { streamText, convertToModelMessages } from 'ai';
import { anthropic } from '@ai-sdk/anthropic';
import Ably from 'ably';
import { createServerTransport } from '@ably/ai-transport/vercel';
const ably = new Ably.Realtime({ key: process.env.ABLY_API_KEY });
export async function POST(req) {
const { messages, history, id, turnId, clientId, forkOf, parent } = await req.json();
const channel = ably.channels.get(id);
const transport = createServerTransport({ channel });
const turn = transport.newTurn({ turnId, clientId, parent, forkOf });
await turn.start();
if (messages.length > 0) {
await turn.addMessages(messages, { clientId });
}
const allMessages = [
...(history ?? []).map((h) => h.message),
...messages.map((m) => m.message),
];
const result = streamText({
model: anthropic('claude-sonnet-4-20250514'),
system: 'You are a helpful assistant.',
messages: await convertToModelMessages(allMessages),
abortSignal: turn.abortSignal,
});
after(async () => {
const { reason } = await turn.streamResponse(result.toUIMessageStream());
await turn.end(reason);
transport.close();
});
return new Response(null, { status: 200 });
}
```
## Create the chat component
Create `app/chat.tsx`. The client uses Vercel's `useChat` hook with the AI Transport adapter. The transport subscribes to the Ably channel and syncs messages across every connected client.
### Javascript
```
'use client';
import { useChat } from '@ai-sdk/react';
import { useActiveTurns, useView } from '@ably/ai-transport/react';
import { useChatTransport, useMessageSync } from '@ably/ai-transport/vercel/react';
import { useState } from 'react';
export function Chat({ chatId }) {
const [input, setInput] = useState('');
const { chatTransport } = useChatTransport();
const { messages, setMessages, sendMessage, stop } = useChat({
id: chatId,
transport: chatTransport,
});
useMessageSync({ setMessages });
useView({ limit: 30 });
const activeTurns = useActiveTurns();
const isStreaming = activeTurns.size > 0;
return (
{messages.map((msg) => (
{msg.role}:{' '}
{msg.parts.map((part, i) =>
part.type === 'text' ? {part.text} : null,
)}
))}
);
}
```
## Wire it together
Create `app/page.tsx`. The `Providers` component sets up an authenticated Ably client using `authUrl: '/auth'`. The `TransportProvider` wires the channel and codec into AI Transport.
### Javascript
```
'use client';
import { useEffect, useState } from 'react';
import * as Ably from 'ably';
import { AblyProvider } from 'ably/react';
import { ChatTransportProvider } from '@ably/ai-transport/vercel/react';
import { Chat } from './chat';
function Providers({ children }) {
const [client, setClient] = useState(null);
useEffect(() => {
const ably = new Ably.Realtime({ authUrl: '/auth' });
setClient(ably);
return () => ably.close();
}, []);
if (!client) return null;
return {children} ;
}
export default function Page() {
const chatId = 'my-chat-session';
return (
);
}
```
Run `npm run dev` and open `http://localhost:3000`. Open a second tab to the same URL; both tabs share the same durable session.
## What is happening
1. The client sends the user message via HTTP POST to your API route.
2. The server publishes the message to the Ably channel, invokes the LLM, and streams tokens to the channel.
3. Every client subscribed to the channel receives tokens in real time.
4. If a client disconnects, it reconnects automatically and resumes from where it left off.
This is a [durable session](https://ably.com/docs/ai-transport/why.md?source=llms.txt#durable-sessions). The HTTP request triggers the agent; all communication flows through the Ably channel.
## Understand the architecture
Vercel AI SDK handles intelligence and UI. AI Transport handles what happens between the model and every device. See [Vercel AI SDK UI](https://ably.com/docs/ai-transport/frameworks/vercel-ai-sdk-ui.md?source=llms.txt) for the client-side integration and [Vercel AI SDK Core](https://ably.com/docs/ai-transport/frameworks/vercel-ai-sdk-core.md?source=llms.txt) for the server-side integration.
Vercel built the `ChatTransport` interface as the extension point for custom transports. AI Transport implements `ChatTransport`, so you swap the transport layer without changing your application code:
### Javascript
```
// Before: default HTTP transport
const { messages } = useChat();
// After: Ably transport (everything else stays the same)
// Wrap your tree with first.
const { chatTransport } = useChatTransport();
const { messages } = useChat({ transport: chatTransport });
```
### Choose an integration path
Both paths use the same server code. The difference is client-side only.
The `useChat` path is the simplest. `useChatTransport` wraps the core transport for direct use with Vercel's `useChat` hook. `useMessageSync` pushes other clients' messages into `useChat` state. You get Vercel's message management with AI Transport's durable delivery. Use this path for the standard Vercel `useChat` developer experience with durable sessions added. This is the path the tutorial above follows.
The [Core SDK](https://ably.com/docs/ai-transport/getting-started/core-sdk.md?source=llms.txt) path uses AI Transport's React hooks (`useView`, `useTree`, `useCreateView`) directly instead of `useChat`. The write operations (`send`, `regenerate`, `edit`, `update`) come back as methods on the `ViewHandle` returned by `useView`. This gives you full access to the conversation tree, branch navigation, split-pane views, and custom message construction. Use this path for branching UI, custom message rendering, or direct control over the conversation tree.
## Explore next
- [Vercel AI SDK UI](https://ably.com/docs/ai-transport/frameworks/vercel-ai-sdk-ui.md?source=llms.txt): client-side integration with `useChat`.
- [Cancellation](https://ably.com/docs/ai-transport/features/cancellation.md?source=llms.txt): the stop button already works because `useChat`'s `stop()` calls `transport.cancel()`.
- [Multi-device sessions](https://ably.com/docs/ai-transport/features/multi-device.md?source=llms.txt): open another tab to see real-time sync.
- [History and replay](https://ably.com/docs/ai-transport/features/history.md?source=llms.txt): `useView` already loads history on mount.
- [Sessions](https://ably.com/docs/ai-transport/concepts/sessions.md?source=llms.txt): durable sessions and how the conversation persists.
## Related Topics
- [Core SDK](https://ably.com/docs/ai-transport/getting-started/core-sdk.md?source=llms.txt): Build a streaming AI chat app using AI Transport's core React hooks. Full access to the conversation tree, branching, and pagination.
## Documentation Index
To discover additional Ably documentation:
1. Fetch [llms.txt](https://ably.com/llms.txt?source=llms.txt) for the canonical list of available pages.
2. Identify relevant URLs from that index.
3. Fetch target pages as needed.
Avoid using assumed or outdated documentation paths.