Quickstart

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 react

The 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:

.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:

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-list

This 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:

app/components/Assistant.client.vue
<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:

server/api/chat.post.ts
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:

app/pages/index.vue
<template>
  <Assistant />
</template>

A page renders only where app/app.vue outputs <NuxtPage />. The nuxi init starter outputs <NuxtWelcome /> there instead, so replace it:

app/app.vue
<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.