如何在2026年构建具备AI的Nuxt应用
在2026年构建Nuxt的AI功能需要三部分,这些环节无需胶水代码即可衔接。Vercel AI SDK 为你提供了流媒体引擎和原生的 Composition API 组合文件,这些组合将模型输出暴露为反应式 Vue 状态。Nuxt UI 将有线的聊天组件连接到这些组合文件,Cloudflare Workers AI 则通过 在边缘进行推理,该接口连接到与任何托管服务商相同的 SDK 表面。useChatuseCompletionuseObject``workers-ai-provider

唯一的分工是在包裹之间。该包包含服务器原语和将模型输出转换为类型化消息流的流适配器,同时存储消耗该数据的可组合文件。你构建两个提供者阶段:通过 Vercel AI Gateway 进行本地基线,覆盖多个模型,通过一个密钥;然后是边缘阶段,将提供商替换为绑定到 Cloudflare 的供应商,运行 Cloudflare 自有模型,没有第三方模型账户。你的应用代码在两者之间几乎没有变化,因为两者实现的是同一个界面。aistreamText,@ai-sdk/vueworkers-ai-providerenv.AI
2026年如何设置Nuxt和Vercel AI SDK?
搭建一个Nuxt应用,安装AI软件包。这使用Node 22和pnpm。
pnpm create nuxt nuxt-ai-2026
cd nuxt-ai-2026
pnpm add ai @ai-sdk/vue zod
你的本地基线路由是通过Vercel AI Gateway,所以你需要Gateway API密钥。从AI Gateway仪表盘创建一个,然后作为运行时配置添加。nuxt.config.ts
export default defineNuxtConfig({
compatibilityDate: '2026-07-31',
devtools: { enabled: true },
runtimeConfig: {
aiGatewayApiKey: ''
}
})
Nuxt 将一个前缀的环境变量映射到匹配的运行时配置上,如何在 Windows 11/10 中自动清空回收站 - 致知笔记 - 专注教程知识分享 因此在运行时填充。放进去。NUXT_NUXT_AI_GATEWAY_API_KEYaiGatewayApiKey``.env
NUXT_AI_GATEWAY_API_KEY=your-key-here
如何使用Nuxt服务器路由流式传输AI响应?
保持模型调用在Nitro路由中,这样你的提供商密钥保持在服务器端,浏览器永远不会触碰模型。该路由使用四个函数:运行模型,将客户端的丰富消息格式简化为模型预期,将原始输出调整为类型化的UI流,并包裹在Nitro可发送的响应中。aistreamTextconvertToModelMessagestoUIMessageStreamcreateUIMessageStreamResponse
import {
streamText,
convertToModelMessages,
createGateway,
createUIMessageStreamResponse,
toUIMessageStream,
type UIMessage
} from 'ai'
export default defineLazyEventHandler(async () => {
const apiKey = useRuntimeConfig().aiGatewayApiKey
if (!apiKey) throw new Error('Missing AI Gateway API key')
const gateway = createGateway({ apiKey })
return defineEventHandler(async (event) => {
const { messages }: { messages: UIMessage[] } = await readBody(event)
const result = streamText({
model: gateway('inclusionai/ling-3.0-flash-free'),
messages: await convertToModelMessages(messages)
})
return createUIMessageStreamResponse({
stream: toUIMessageStream({ stream: result.stream })
})
})
})
封装是第一次请求时构建一次网关,而不是每次通话。输入的对象是携带有序数组而非普通字符串的对象,这就是为什么在交给模型前要先将其压平。defineLazyEventHandlermessagesUIMessagepartsconvertToModelMessages
在构建任何接口之前,先确认路由流。运行开发服务器,然后发送原始请求。
curl -N -X POST http://localhost:3000/api/chat \
-H "Content-Type: application/json" \
-d '{"messages":[{"id":"1","role":"user","parts":[{"type":"text","text":"Say hello in one short sentence."}]}]}'
端点通过服务器发送的事件响应,如何让运行缓慢的 iPhone 提速 - 致知笔记 - 专注教程知识分享 每个事件一行携带一个类型的区块。data:
每个类型部分运行一个起点、一系列的 delta 和一个结束。该模型先流式处理推理部分,再流文本部分,这样你可以将思考与答案分开渲染。这些 delta 是片段,不是完整的单词,这个片段流就是你的接口逐个标记渲染的。
可组合设备在Vue中是如何工作的?useChat
可组合文件将Vue组件连接到你的流媒体路由,并将对话暴露为反应状态。无参数调用,默认会发布到;因此,它与前一段的路线搭配,无需任何交通配置。useChat``/api/chat
<script setup lang="ts">
import { useChat } from '@ai-sdk/vue'
import { ref } from 'vue'
const input = ref('')
const { messages, sendMessage, status } = useChat()
function handleSubmit(e: Event) {
e.preventDefault()
if (!input.value.trim()) return
sendMessage({ text: input.value })
input.value = ''
}
</script>
<template>
<div v-for="(m, i) in messages" :key="m.id ?? i">
<strong>{{ m.role === 'user' ? 'You' : 'AI' }}</strong>
<div v-for="(part, j) in m.parts" :key="`${m.id}-${j}`">
<div v-if="part.type === 'reasoning'">{{ part.text }}</div>
<div v-else-if="part.type === 'text'">{{ part.text }}</div>
</div>
</div>
<form @submit="handleSubmit">
<input v-model="input" placeholder="Say something..." />
</form>
</template>
messages`是一个Vue引用,会在每个流式分块更新,因此模板会在响应到来时逐个令牌重新渲染。你是迭代而不是单一字符串,因为每个消息都包含一个有序的输入部分数组,通过不同的分支来渲染推理和文本,正是让你能让模型思维风格与其回答不同的原因。 打开流,通过 , ,你可以驱动加载状态。`m.parts``sendMessage``status``submitted``streaming``ready
这会渲染对话内容,但显示模型的原始输出,Markdown 标题、代码围栏和表格作为字面字符出现,因为这里没有解析它们。下一部分用Nuxt UI的聊天组件替换了手写标记,并将这些Markdown加载成格式化输出。
如何在Nuxt UI中为AI聊天做样式?
Nuxt UI 提供为 AI 聊天设计的组件,直接调用 AI SDK 的消息部分,因此你会获得滚动的消息列表、提交和停止处理的提示,以及专门用于推理和工具调用的块,无需自己写标记。Comark渲染流式Markdown,解析代币到达时,使格式化输出实时积累,而不是最后闪现。
安装Nuxt UI、Comark、Shiki语言以及Shiki本身。Comark的Highlight插件依赖于pnpm,除非你声明,否则pnpm不会暴露。shiki
pnpm add @nuxt/ui @comark/nuxt @shikijs/langs shiki
注册两个模块,并将 Nuxt 指向 CSS 条目。
export default defineNuxtConfig({
compatibilityDate: '2026-07-31',
devtools: { enabled: true },
modules: ['@nuxt/ui', '@comark/nuxt'],
css: ['~/assets/css/main.css'],
runtimeConfig: {
aiGatewayApiKey: ''
}
})
app/assets/css/main.css`导入 Tailwind 和 Nuxt UI,并将应用包裹在 中,Nuxt UI 需要用于叠加层和吐槽。`app/app.vue``UApp
@import "tailwindcss";
@import "@nuxt/ui";
<template>
<UApp>
<NuxtPage />
</UApp>
</template>
用高亮插件定义一个可重复使用的 Comark 渲染器,如何在 iPhone 上的 YouTube 中设置睡眠定时器 - 致知笔记 - 专注教程知识分享 让 Markdown 辅助渲染带有语法高亮的代码块。这成为你在页面中使用的一个组件。
import highlight from '@comark/nuxt/plugins/highlight'
export default defineComarkComponent({
name: 'ChatComark',
plugins: [highlight()],
class: '*:first:mt-0 *:last:mb-0'
})
现在接线。 渲染列表并自动滚动,它的槽会把每个消息交给你,这样你就能决定每个部分的渲染方式。你在部分类型上分支,将 guards 分支,发送推理到 , assistant text 到 , , 用户文本 组成普通段落,而 from 则告诉某个部分是否仍在流媒体,这样 Comark 可以增量渲染。UChatMessagesstatus#contentaiUChatReasoning<ChatComark>isPartStreaming``@nuxt/ui/utils/ai
<script setup lang="ts">
import { isReasoningUIPart, isTextUIPart } from 'ai'
import { useChat } from '@ai-sdk/vue'
import { isPartStreaming } from '@nuxt/ui/utils/ai'
const input = ref('')
const { messages, status, error, sendMessage, regenerate, stop } = useChat()
function onSubmit() {
if (!input.value.trim()) return
sendMessage({ text: input.value })
input.value = ''
}
</script>
<template>
<UContainer class="min-h-dvh flex flex-col py-6">
<UChatMessages :messages="messages" :status="status" should-auto-scroll class="flex-1">
<template #content="{ message }">
<template v-for="(part, index) in message.parts" :key="`${message.id}-${part.type}-${index}`">
<UChatReasoning
v-if="isReasoningUIPart(part)"
:text="part.text"
:streaming="isPartStreaming(part)"
>
<ChatComark :markdown="part.text" :streaming="isPartStreaming(part)" />
</UChatReasoning>
<template v-else-if="isTextUIPart(part)">
<ChatComark
v-if="message.role === 'assistant'"
:markdown="part.text"
:streaming="isPartStreaming(part)"
/>
<p v-else-if="message.role === 'user'" class="whitespace-pre-wrap">
{{ part.text }}
</p>
</template>
</template>
</template>
</UChatMessages>
<UChatPrompt v-model="input" :error="error" class="sticky bottom-0" @submit="onSubmit">
<UChatPromptSubmit :status="status" @stop="stop()" @reload="regenerate()" />
</UChatPrompt>
</UContainer>
</template>
UChatPromptSubmit`根据中流布线切换发送、停止和重新装填,并重试。你的推理现在以可折叠的块形式渲染,答案以格式化的Markdown格式流传,配有真实表格和高亮代码,这些都不需维护。`status``stop``regenerate
如何用Nuxt在边缘运行Cloudflare Workers AI?
网关路由通过API密钥到达托管模型。如果要在边缘运行推理,可以直接绑定 Cloudflare Workers AI,这样模型就运行在 Cloudflare 的网络上,没有第三方模型账户。由于实现了相同的 AI SDK 接换只涉及提供者和模型 ID,流、可组合和 Nuxt UI 组件保持原样。workers-ai-provider
安装供应商和Wrangler。
pnpm add workers-ai-provider
pnpm add -D wrangler
声明AI绑定,连同工人条目和Nitro Cloudflare构建产生的静态资产一起。Workers AI 没有本地仿真,所以绑定总是针对 Cloudflare 的网络运行,即使在开发阶段也是如此。wrangler.jsonc
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "nuxt-ai-2026",
"compatibility_date": "2026-07-31",
"compatibility_flags": ["nodejs_compat"],
"main": ".output/server/index.mjs",
"assets": {
"directory": ".output/public",
"binding": "ASSETS"
},
"ai": {
"binding": "AI"
}
}
把 Nitro 指向 Cloudflare Workers 预设,这样构建就能针对该运行时。
export default defineNuxtConfig({
compatibilityDate: '2026-07-31',
devtools: { enabled: true },
modules: ['@nuxt/ui', '@comark/nuxt'],
css: ['~/assets/css/main.css'],
nitro: {
preset: 'cloudflare_module'
},
runtimeConfig: {
aiGatewayApiKey: ''
}
})
你的边缘路由会镜像网关路由,交换提供商。 取绑定,Cloudflare预设在 上暴露了该绑定。createWorkersAIAIevent.context.cloudflare.env
import {
streamText,
convertToModelMessages,
createUIMessageStreamResponse,
toUIMessageStream,
type UIMessage
} from 'ai'
import { createWorkersAI } from 'workers-ai-provider'
export default defineEventHandler(async (event) => {
const { messages }: { messages: UIMessage[] } = await readBody(event)
const workersAI = createWorkersAI({ binding: event.context.cloudflare.env.AI })
const result = streamText({
model: workersAI('@cf/meta/llama-3.2-3b-instruct'),
messages: await convertToModelMessages(messages)
})
return createUIMessageStreamResponse({
stream: toUIMessageStream({ stream: result.stream })
})
})
使用当前的Workers AI文本生成模型,因为Cloudflare已弃用旧模型。列出你的账号可以运行什么,并选择一个生成文本的条目。npx wrangler ai models
要在本地运行 Cloudflare 运行时,先构建,然后启动 ,这为构建的 Worker 提供真实绑定。wrangler dev
pnpm build
npx wrangler dev
确认路线的流向形状和之前一样。
curl -N -X POST http://localhost:8787/api/chat-edge \
-H "Content-Type: application/json" \
-d '{"messages":[{"id":"1","role":"user","parts":[{"type":"text","text":"Say hello in one short sentence."}]}]}'
帧序列与网关路由完全匹配,因此Nuxt UI的组件渲染该流时没有任何更改。唯一的区别在于内容,而非形状:该模型只输出文本部分,因为 Llama 3.2 3B 不产生推理,因此分支从未触发。UChatReasoning
如何部署到Cloudflare?
有了Cloudflare预设和绑定,你只需一个命令即可部署。Wrangler 基于输出 构建,上传 Worker 及其静态资产,并附加声明的绑定。wrangler.jsonc``pnpm build
npx wrangler deploy
它会报告上传的素材、附加的绑定以及你子域名的实时URL。workers.dev
Success! Uploaded 115 files
Your Worker has access to the following bindings:
Binding Resource
env.AI AI
env.ASSETS Assets
Deployed nuxt-ai-2026 triggers
https://nuxt-ai-2026.<your-subdomain>.workers.dev
你的绑定从本地开发带到生产环境不变,所以部署的边缘路由运行的是同样的 Workers AI 推理,无需额外配置。直接点击实时端点流来确认它。AI
curl -N -X POST https://nuxt-ai-2026.<your-subdomain>.workers.dev/api/chat-edge \
-H "Content-Type: application/json" \
-d '{"messages":[{"id":"1","role":"user","parts":[{"type":"text","text":"Say hello in one short sentence."}]}]}'
你得到的类型流和本地运行是一样的,所以界面在生产环境中表现相同。
你可以实时监控请求击中已部署的工人,该程序会打印每个调用和服务器端的错误。新部署可能需要几秒钟才能在边缘上传播,因此部署后立即触发的请求可能会短暂返回Cloudflare错误,然后Worker就已全部在线。npx wrangler tail nuxt-ai-2026
Nuxt 中的 Edge AI 流媒体有多快?
两条路由部署到同一个工作者,因此基于它们的基准测试网络距离相同,并隔离了推理路径的差异。该线束在每条路由发送十个请求,记录第一个令牌和吞吐量,并报告各次运行的中位数。第一个令牌的时间捕捉连接和模型启动,而第一个令牌后的吞吐量反映流后的生成速度。
const BASE = 'https://nuxt-ai-2026.<your-subdomain>.workers.dev'
const PROMPT = 'Explain what a hash map is in three sentences.'
async function measure(route) {
const start = performance.now()
let firstTokenAt = null
let tokenCount = 0
const res = await fetch(`${BASE}${route}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
messages: [{ id: '1', role: 'user', parts: [{ type: 'text', text: PROMPT }] }]
})
})
const reader = res.body.getReader()
const decoder = new TextDecoder()
while (true) {
const { done, value } = await reader.read()
if (done) break
for (const line of decoder.decode(value).split('\n')) {
if (line.includes('"type":"text-delta"')) {
if (firstTokenAt === null) firstTokenAt = performance.now()
tokenCount++
}
}
}
const end = performance.now()
return {
ttft: firstTokenAt - start,
tps: tokenCount / ((end - firstTokenAt) / 1000)
}
}
测量中位数,对已部署工人的测量:
| 路线 | 模型 | TTFT中位数 | 吞吐量中位数 |
|---|---|---|---|
| 门户 | ling-3.0-flash-free | 1943 毫秒 | 103.5 tok/s |
| 工人AI优势 | llama-3.2-3b-instruct | 498毫秒 | 504.0 tok/s |
边缘路由到达第一个令牌的速度大约是其四倍,但这并不是一个干净的边缘与托管结果,因为网关模型是一个思考后回答的推理模型,而边缘模型是一个小型指令模型,能够立即输出文本,因此部分差距在于模型行为而非传输。
结论
Vue和Nuxt生态系统现在交付了生产型AI功能所需的一切,从可组合文件到Nuxt UI的聊天组件,再到在边缘运行推理的Workers AI绑定。将托管网关换成边缘绑定后,一条提供者线和一个模型ID都被修改了,因为两者都实现了相同的AI SDK接口,且相同的组件渲染了两条流,且没有任何编辑。以前只有React的版本,现在Nuxt里变成了一个一命令部署。@ai-sdk/vue

浙公网安备 33010602011771号