如何在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

posted @ 2026-09-15 13:50  panxianren  阅读(5)  评论(0)    收藏  举报