### Shell
```
npm install @ably/ai-transport ably ai @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.
The client below uses `authUrl: '/auth'` to fetch tokens from this endpoint.
## Create the server route
Create `app/api/chat/route.ts`. The server creates a turn, publishes the user message, streams the LLM response through the Ably channel, and returns immediately. `streamText` orchestrates the model, and `UIMessageCodec` encodes its output for Ably. Swap either component if your stack is different.
### 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';
import { UIMessageCodec } 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, codec: UIMessageCodec });
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 component uses AI Transport's core hooks directly. `useView` returns the visible messages, a `send` function, and pagination:
### Javascript
```
'use client';
import { useClientTransport, useActiveTurns, useView } from '@ably/ai-transport/react';
import { useState } from 'react';
export function Chat({ chatId }) {
const [input, setInput] = useState('');
const { transport } = useClientTransport();
const { messages, send, hasOlder, loadOlder } = useView({ limit: 30 });
const activeTurns = useActiveTurns();
const isStreaming = activeTurns.size > 0;
const handleSubmit = async (e) => {
e.preventDefault();
if (!input.trim()) return;
const text = input;
setInput('');
await send({
id: crypto.randomUUID(),
role: 'user',
parts: [{ type: 'text', text }],
});
};
return (
{hasOlder && (
)}
{messages.map((msg) => (
{msg.role}:{' '}
{msg.parts.map((part, i) =>
part.type === 'text' ? {part.text} : null,
)}
))}
);
}
```
## Wire it together
Create `app/page.tsx`. `Providers` sets up an authenticated Ably client. `TransportProvider` wires the channel and codec into AI Transport. `UIMessageCodec` matches the server's output format; supply a different codec if your model layer produces different events.
### Javascript
```
'use client';
import { useEffect, useState } from 'react';
import * as Ably from 'ably';
import { AblyProvider } from 'ably/react';
import { TransportProvider } from '@ably/ai-transport/react';
import { UIMessageCodec } from '@ably/ai-transport/vercel';
import { Chat } from './chat';
function Providers({ children }) {
const [client, setClient] = useState(null);
useEffect(() => {
const ably = new Ably.Realtime({ authUrl: '/auth', clientId: 'user' });
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 to see 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.
`useView` subscribes to the conversation tree and returns the visible messages along the currently selected branch. You have direct access to the tree: branching, pagination, and sibling navigation.
## Explore next
- [Conversation branching](https://ably.com/docs/ai-transport/features/branching.md?source=llms.txt): use `useView`'s `getSiblings` and `select` to navigate branches.
- [Conversation branching](https://ably.com/docs/ai-transport/features/branching.md?source=llms.txt): use `useView`'s `edit`, `regenerate`, and `select` to fork and navigate conversations.
- [History and replay](https://ably.com/docs/ai-transport/features/history.md?source=llms.txt): `useView` loads history on mount via the `limit` option.
- [Sessions](https://ably.com/docs/ai-transport/concepts/sessions.md?source=llms.txt): durable sessions and how the conversation persists.
- [React API reference](https://ably.com/docs/ai-transport/api/react/providers.md?source=llms.txt): providers and the per-hook pages.
## Related Topics
- [Vercel AI SDK](https://ably.com/docs/ai-transport/getting-started/vercel-ai-sdk.md?source=llms.txt): Build a streaming AI chat app with Vercel AI SDK and Ably AI Transport in a few minutes. Durable sessions, multi-device sync, and cancellation out of the box.
## 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.