Build a streaming Nuxt chat with Vue, assistant-ui, and the AI SDK.
This guide adds a streaming chat to a Nuxt app in about five minutes. The runtime stays in a client-only component and the model call stays in a Nitro route.
Install the runtime
Install the Vue binding, the AI SDK runtime, and an AI SDK provider:
pnpm add @assistant-ui/vue @assistant-ui/ai-sdk ai @ai-sdk/openai reactThe AI SDK runtime runs on react. It is an optional peer of @assistant-ui/ai-sdk, so it does not install on its own.
Add your provider key to .env:
OPENAI_API_KEY="sk-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"Install the styled components
The styled components install with the shadcn-vue CLI. If the app does not use shadcn-vue yet, set up Tailwind CSS and run npx shadcn-vue@latest init as the shadcn-vue Nuxt guide describes.
Then add the assistant-ui registry once in components.json:
{
"registries": {
"@assistant-ui": "https://r.assistant-ui.com/vue/{name}.json"
}
}Install the chat surface:
npx shadcn-vue@latest add @assistant-ui/thread @assistant-ui/thread-listThis writes the Vue components to app/components/assistant-ui and installs their dependencies.
Create the assistant client
Use a .client.vue file so the runtime is created only in the browser:
<script setup lang="ts">
import { AuiConfig, AuiProvider } from "@assistant-ui/vue";
import { AISDKChat } from "@assistant-ui/ai-sdk";
import Thread from "~/components/assistant-ui/thread.vue";
const config = AuiConfig({
threads: AISDKChat(),
});
</script>
<template>
<AuiProvider :config="config">
<div class="h-dvh">
<Thread />
</div>
</AuiProvider>
</template>AISDKChat() creates one streaming thread. Its default transport sends requests to /api/chat.
Add the streaming route
Create the Nitro route that receives UI messages and returns an AI SDK UI message stream:
import { openai } from "@ai-sdk/openai";
import { convertToModelMessages, streamText, type UIMessage } from "ai";
export default defineEventHandler(async (event) => {
const abortController = new AbortController();
event.node.res.on("close", () => {
if (!event.node.res.writableFinished) {
abortController.abort();
}
});
const { messages, system } = await readBody<{
messages: UIMessage[];
system?: string;
}>(event);
const result = streamText({
abortSignal: abortController.signal,
model: openai("gpt-6-luna"),
messages: await convertToModelMessages(messages),
system,
});
return result.toUIMessageStreamResponse();
});Render the assistant
Nuxt recognizes the .client.vue suffix and only mounts this component in the browser:
<template>
<Assistant />
</template>A page renders only where app/app.vue outputs <NuxtPage />. The nuxi init starter outputs <NuxtWelcome /> there instead, so replace it:
<template>
<NuxtPage />
</template>Start the app with pnpm dev, then send a message. The response streams from server/api/chat.post.ts into the thread.
Next steps
Add a thread list with AISDKThreads, customize the styled components, render tool calls with Tool UI, and read Server rendering for the .client.vue boundary.