普通视图

发现新文章,点击刷新页面。
昨天 — 2026年1月17日首页

Nuxt 写后端

作者 江湖文人
2026年1月17日 09:22

写接口

// server/api/test.ts

export default defineEventHandler(async (event) => {
  // 写这个接口的逻辑
})

可以直接返回textjsonhtmlstream(文件流等流)

放心好了,nuxt支持热模块替换和自动导入。改代码直接看到效果。无需手动写import语句。

// vue
<script setup>
// 要写import
import { ref } from 'vue'
import MyButton from '@/components/MyButton.vue'
import { useFetch } from '@/composables/useFetch'

const count = ref(0)
const { data } = useFetch('/api/data')
</script>

<template>
  <MyButton>点击</MyButton>
</template>

Nuxt的自动导入

<script setup>
const count = ref(0) // 自动从`vue`导入ref
const { data } = useFetch('/api/data') // 自动从 composables/ 导入
</script>

<template>
  <MyButton>点击</MyButton>
</template>

Nuxt自动导入了哪些

  • Vue APIrefcomputedonMounted

  • Nuxt ComposablesuseFetchuseAsyncData

  • Vue RouteruseRouteruseRoute

  • 组件components/目录下的所有组件

  • 工具函数utils/composables/目录下的函数

  • VueUse:如果安装了@vueuse/nuxt

<template>
  <div>
    <h1>{{ title }}</h1>
    <MyComponent />
  </div>
</template>

<script setup>
// 1. 不需要导入 MyComponent - 自动导入
// 2. 修改后页面局部更新 - HMR
const title = ref('欢迎') // ref 也是自动导入的
</script>

部署 - 通用

云服务器构建Nuxt应用

  • Cloudflare
  • Netlify
  • Edge

混合渲染

自定义路由

// nuxt.config.ts

export default defineNuxtConfig({
  routeRules: {
    // 为 SEO 目的在构建时生成
    '/': { prerender: true },
    '/api/*': { cache: { maxAge: 60 * 60 } },
    '/old-page': {
      redirect: { to: '/new-page', statusCode: 302 }
    }
  }
})

目录结构

  • 通过nuxt.configapp.config在项目之间共享可重用的配置预设。
  • components/目录做组件库。
  • composables/utils/目录创建工具和组合式函数库。
  • layers/目录做项目的层

每个层的srcDir都会自动创建命名的层别名。可以用#layers/test访问~~/layers/test层。

也可以自定义nuxt.config文件去设置添加extends去加一个层:

// nuxt.config.ts

export default defineNuxtConfig({
  extends: [
    '../base', // 从本地层去加
    '@my-themes/awesome', // 从安装的包去加
    'github:my-themes/awesome#v1' // 从git库中加
  ]
})

github私有库的要加token

// nuxt.config.ts

export default defineNuxtConfig({
  extends: [
    '../base', // 从本地层去加
    '@my-themes/awesome', // 从安装的包去加
    ['github:my-themes/awesome#v1': { auth: process.env.GITHUB_TOKEN }] // 从git库中加
  ]
})

起别名

// nuxt.config.ts
export default defineNuxtConfig({
  extends: [
    [
      'github:my-themes/awesome',
      { 
        meta: {
          name: 'my-awesome-theme',
        },
      },
    ],
  ]
})

预渲染

Nuxt允许页面在构建时进行静态渲染,提高SEO。

为啥选Nuxt。它SEO优秀啊。在应用中,我们可以选几个页面在构建时进行渲染。有请求时,Nuxt会提供预构建的页面,而不是动态生它们。

基于爬取的预渲染

nuxt generate命令。通过Nitro爬虫去建和预渲染应用。

建站点,启动一个nuxt实例。

选择性预渲染

// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    prerender: {
      routes: ["/user/1", "/user/2"],
      ignore: ["/dynamic"],
    },
  },
});
昨天以前首页

详细说一下nuxt generate是干啥的

作者 江湖文人
2026年1月16日 18:21

nuxt generate 是 Nuxt.js 框架的一个**静态站点生成(Static Site Generation, SSG)**命令。我来详细解释一下它的含义和作用:

是什么?

nuxt generate 命令会将你的 Nuxt 应用预渲染为静态 HTML 文件。它会:

  • 为每个路由生成对应的 HTML 文件
  • 将生成的静态文件保存在 dist/ 目录中
  • 包含必要的 CSS、JavaScript 和资源文件

主要作用

1. 性能优化

  • 预生成的 HTML 文件无需服务器端渲染,加载速度极快
  • CDN 友好,可以轻松缓存
  • 减少服务器压力和响应时间

2. SEO 优化

  • 搜索引擎可以直接抓取静态 HTML 内容
  • 更好的 SEO 表现(相比于纯客户端渲染)

3. 部署简单

  • 生成的文件可以部署到任何静态主机:
    • Netlify、Vercel、GitHub Pages
    • AWS S3、Firebase Hosting
    • Nginx、Apache 等传统服务器

4. 成本效益

  • 无需专门的 Node.js 服务器
  • 可以使用廉价的静态托管服务

使用场景

适合使用 nuxt generate

内容型网站:博客、文档、营销页面
数据不频繁变化:产品展示页、公司官网
需要优秀 SEO 的应用
高访问量 的只读页面

不适合使用(需要考虑 SSR 或 CSR):

用户个性化内容:每个用户看到的内容不同
实时数据:股票行情、聊天应用
频繁更新:社交媒体动态
需要身份验证 的页面(可通过混合模式解决)

基本使用

# 生成静态文件
nuxt generate

# 生成后预览
nuxt generate && nuxt start

# 构建并生成(常用)
npm run generate
# 在 package.json 中通常配置为:
# "scripts": {
#   "generate": "nuxt generate"
# }

配置示例

// nuxt.config.js
export default {
  target: 'static', // 明确指定为静态站点
  generate: {
    // 动态路由需要指定
    routes: [
      '/users/1',
      '/users/2',
      '/blog/post-1'
    ],
    // 或者异步获取路由
    async routes() {
      const posts = await $fetch('/api/posts')
      return posts.map(post => `/blog/${post.id}`)
    }
  }
}

工作流程

执行 nuxt generate
    ↓
Nuxt 启动构建过程
    ↓
为每个路由生成 HTML
    ↓
提取 CSS 和 JavaScript
    ↓
保存到 dist/ 目录
    ↓
完成!可以部署到任何静态主机

nuxt build 的区别

  • nuxt generate:生成静态 HTML 文件,用于静态托管
  • nuxt build:构建应用,用于服务器端渲染(SSR)部署

高级特性

1. 混合模式

// 部分页面静态生成,部分页面动态渲染
export default {
  generate: {
    exclude: [
      '/dashboard',  // 这个页面保持动态
      '/admin/**'    // 所有 admin 页面都动态
    ]
  }
}

2. 增量静态再生

可以通过定时任务重新生成部分页面。

实际示例

# 1. 创建 Nuxt 项目
npx nuxi@latest init my-static-site

# 2. 安装依赖
cd my-static-site
npm install

# 3. 生成静态文件
npm run generate

# 4. 查看生成的文件
ls -la dist/
# 会看到 index.html, about.html 等

# 5. 本地测试生成的文件
npx serve dist/

总之,nuxt generate 是 Nuxt.js 强大的静态站点生成功能,特别适合需要优秀性能、SEO 和低成本部署的场景。对于适合静态化的项目,它能提供极佳的用户体验。

Nuxt state状态如何管理,3秒手把手帮你

作者 江湖文人
2026年1月16日 17:01

useState。有响应式和支持ssr共享。

useState是支持ssrref替代方案。其中,在后端渲染后(前端水合)会被保留,并通过唯一键在所有组件之间共享。

useState中的数据将序列化为JSON

例子

基本用法

用组件本地的计算器状态。任何用useState('counter')的组件都共享相同的响应式状态。

// app.vue
<script setup lang="ts">
const counter = useState('counter', () => Math.round(Math.random() * 1000))
</script>

<template>
  <div>
    计算器:{{ counter }}
    <button @click="counter++">
      +
    </button>
    <button @click="counter--">
      -
    </button>
  </div>
</template>

初始化状态

异步解析去初始化状态。

// app.vue
<script setup lang="ts">
const websiteConfig = useState('config')

await callOnce(async () => {
  websiteConfig.value = await $fetch('https://my-cms.com/api/website-config')
})
</script>

与Pinia一起用

Pinia 模块创建全局存储并在整个应用中使用它。

// stores/website.ts
export const useWebsiteStore = defineStore('websiteStore', {
  state: () => ({
    name: '',
    description: ''
  }),
  actions: {
    async fetch() {
      const infos = await $fetch('https://api.nuxt.com/modules/pinia')
      
      this.name = infos.name
      this.description = infos.description
    }
  }
})

高级用法

// composables/locale.ts

export const useLocale = () => {
  return useState('locale', () => useDefaultLocale().value)
}

export cosnt useDefautLocale = (fallback = 'en-US') => {
  const locale = ref(fallback)
  return locale
}

export const useLocales = () => {
  const locale = useLocale()
  const locales = ref([
    'en-US',
    'en-GB',
    ...
  ])
  if (!locales.value.includes(locale.value)) {
    locales.value.unshift(locale.value)
  }
  return locales
}

export const useLocaleDate = (date: Ref<Date> | Date, locale = useLocale()) => {
  return computed(() => new Intl.DateTimeFormat(locale.value, {
    dateStyle: 'full'
  }).format(unref(date)))
}
// app.vue
<script setup lang="ts">
const locales = useLocales()
const locale = useLocale()
const date = useLocaleDate(new Date('2026-1-16'))
</script>

<template>
  <div>
    <h1>生日</h1>
    <p>{{ date }}</p>
    <label for="locale-chooser">语言</label>
    <select id="locale-chooser" v-model="locale">
      <option v-for="loc of locales" :key="loc" :value="loc">
        {{ loc }}
      </option>
    </select>
  </div>
</template>

共享状态

自动导入的组合式函数

// composables/states.ts

export const useColor = () => useState<string>('color', () => 'pink')
// app.vue
<script setup lang="ts">
const useColor = () => useState<string>('color', () => 'pink')

const color = useColor() // 与 useState('color')相同
</script>

<template>
  <p>{{ color }}</p>
</template>

用库 - 第三方的

  • Pinia
  • Harlem
  • XState

Nuxt 请求后端接口怎么写,一篇文章讲清楚

作者 江湖文人
2026年1月16日 15:20

数据获取

Nuxt提供了组合式API去处理应用中的数据获取。

Nuxt内置了两个组合式API和一个库,用于在浏览器或者服务器环境中执行数据获取:useFetchuseAsyncData$fetch

简而言之:

  • $fetch是发起网络请求的最简单方式。
  • useFetch$fetch的封装,在通用渲染中只会获取数据一次。
  • useAsyncDatauseFetch类似,但提供更精细的控制。

useFetchuseAsyncData共享一组通用选项和模式。

为什么需要useFetchuseAsyncData

Nuxt是一个可以在服务器和客户端环境中运行同构(或通用)代码的框架。如果在Vue组件的setup函数中使用$fetch函数进行数据获取,可能会导致数据被获取两次:一次在服务器(用于渲染HTML),另一次在客户端(当HTML被激活时)。这可能会导致激活问题、增加交互时间并引发不可预测的行为。

useFetchuseAsyncData组合式API通过确保如果在服务器上发起了API调用,数据会被转发到客户端的有效载荷中,从而解决了这个问题。

有效载荷是一个可通过useNuxtApp().payload访问得JavaScript对象。它在客户端欧根于避免在激活期间在浏览器中重新获取相同的数据。

// app.vue
<script setup lang="ts">
const {} = await useFetch('/api/data')

async function handleFormSubmit() {
  const res = await $fetch('/api/submit', {
    method: 'POST',
    body: {
      // 我的表单数据
    }
  })
}
</script>

<template>
  <div v-if="data == undefined">
    无数据
  </div>
  <div v-else>
    <form @submit="handleFormSubmit">
      <!-- 表单输入标签 -->
    </form>
  </div>
</template>

在上面的示例中,useFetch会确保请求在服务器上发生,并正确转发到浏览器。$fetch没有这种机制,更适合仅从浏览器发起请求的场景。

Suspense

Nuxt在底层使用Vue<Suspense>组件,以防止在所有异步数据可用于视图之前进行导航。本质是为了防止页面过早跳转,要等数据加载完再允许用户进入页面。目的是避免用户过早进入页面,要等数据全准备好才行。

可添加<NuxtLoadingIndicator>在页面导航之间添加进度条。

$fetch

Nuxt包含ofetch库,并在整个应用中自动导入为全局的$fetch别名。

// pages/todos.vue
<script setup lang="ts">
async function addTodo() {
  const todo = await $fetch('/api/todos', {
    method: 'POST',
    body: {
      // 我的待办数据
    }
  })
}
</script>

仅使用$fetch不会提供网络请求去重和导航阻止。建议在客户端交互(基于事件)时使用$fetch,或者在获取初始组件数据与useAsyncData结合使用。

将客户端标头传递到API

当在服务器上调用useFetch时,Nuxt将使用useRequestFetch来代理客户端标头和Cookie(除了不打算转发的标头,如host)。

<script setup lang="ts">
const { data } = await useFetch(`/api/echo`);
</script>
// /api/echo.ts
export default defineEventHandler(event => parseCookies(event))

或者,下面的示例展示了如何使用useRequestHeaders从服务器端请求(源自客户端)访问Cookie并将其发送到API。使用同构的$fetch调用,确保API端点可以访问用户浏览器最初发送的相同的cookie标头。这仅在不使用useFetch时才需要。

<script setup lang="ts">
const headers = useRequestHeaders(['cookie'])

async function getCurrentUser() {
  return await $fetch('/api/me', { headers })
}
</script>

可以用useRequestFetch自动将标头代理到调用中。

在将标头代理到外部API之前要非常小心,只包含需要的标头。并非所有标头都可以安全地绕过,可能会引入不必要的行为。

以下是不应代理的常见标头列表:

  • host
  • accept
  • content-length
  • content-md5
  • content-type
  • x-forwarded-host
  • x-forwarded-port
  • x-forwarded-proto
  • cf-connecting-ip
  • cf-ray

useFetch

useFetch组合式API在底层用$fetch,用于在setup函数中发起SSR安全地网络请求。

// app.vue
<script setup lang="ts">
const { data: count } = await useFetch('/api/count') 
</script>

<template>
  <p>页面访问量:{{ count }}</p>
</template>

这个组合式API是useAsyncData组合式API$fetch工具的封装

useAsyncData

useAsyncData组合式API负责包装异步逻辑,并在解析后返回结果。

useFetch(url)几乎等同于useAsyncData(url, () => event.$fetch(url))

在某些情况下,使用useFetch组合式API并不合适,例如当CMS或第三方提供自己的查询层时。在这种情况下,可以使用useAsyncData去包装调用。同时仍然保留该组合式API提供的优势。

// pages/users.vue
<script setup lang="ts">
const { data, error } = await useAsyncData('users', () => myGetFunction('user'))

// 也是可能的:
const { data, error } = await userAsyncData(() => myGetFunction('users'))
</script>
useAsyncData的第一个参数是一个唯一键,用于缓存第二个参数(查询参数)的响应。如果直接传递查询函数,这个键可以忽略,它将自动生成。

由于自动生成的键仅考虑调用`useAsyncData`的文件和行,因此建议始终创建自己的键以避免不必要的行为,例如当你创建自己的自定义组合式API来包装`useAsyncData`时。

设置键有助于通过`useNuxtData`在组件之间共享相同的数据,或者刷新特定数据。
// pages/users/[id].vue

<script setup lang="ts">
const { id } = useRoute().params

const { data, error } = await useAsyncData(`user:${id}`, () => {
  return myGetFunction('users', { id })
})
</script>

useAsyncData组合式API是包装并等待多个$fetch请求完成,然后处理结果的好方法。

<script setup lang="ts">
const { data: discounts, status } = await useAsyncData('cart-discount', async () => {
  const [coupons, offers] = await Promise.all([
    $fetch('/cart/coupons'),
    $fetch('/cart/offers')
  ])
  
  return { coupons, offers }
})
// discounts.value.coupons
// discounts.value.offers
</script>

useAsyncData用于获取和缓存数据,而不是触发副作用(如调用Pinia actions),因为这可能导致意外行为,例如使用空值重复执行。

<script setup lang="ts">
const offersStore = useOffersStore()

// 不能这样写
await useAsyncData(() => offtersStore.getOffer(route.params.slug))
</script>

返回值

useFetchuseAsyncData具有相同的返回值,如下所列。

  • data:传入的异步函数的结果。
  • refresh/execute:可用于刷新handler函数返回的数据的函数。
  • clear:可用于将data设置为undefined(或如果提供了options.default()则设置为其值)、将error设置为undefined、将status设置为idle并将任何当前挂起的请求标记为已取消的函数。
  • error:数据获取失败时的错误对象。
  • status:表示数据请求状态的字符串(idlependingsuccesserror)。

dataerrorstatusVue的ref,在<script setup>中通过.value访问。

默认情况下,Nuxt会等待refresh完成后才允许再次执行。

选项

useAsyncDatauseFetch返回相同的对象类型,并接受一组共同的选项作为最后一个参数。它们可以帮助你控制组合式API的行为,如导航阻塞缓存或者执行

延迟加载(Lazy

默认情况下,数据获取组合式API会通过使用VueSuspense等待其异步函数解析后再导航到新页面。可以使用lazy选项在客户端导航时忽略此功能。在这种情况下,将不得不使用status值手动处理加载状态。

// app.vue
<script setup lang="ts">
const { status, data: posts } = useFetch('/api/posts', {
  lazy: true
})
</script>

<template>
  <!-- 需要处理加载状态 -->
  <div v-if="status === 'pending'">
    加载中...
  </div>
  <div v-else>
    <div v-for="post in posts">
      <!-- 处理数据 -->
    </div>
  </div>
</template>

也可以用useLazyFetchuseLazyAsyncData作为便捷方法来执行相同的操作。

<script setup lang="ts">
const { status, data: posts } = useLazyFetch('/api/posts')
</script>

仅客户端获取

默认情况下,数据获取组合式API将在客户端和服务器环境中执行其异步函数。将server选项设置为false以仅在客户端执行调用。在初始加载时,数据将不会在激活前获取。这意味着即使你在客户端等待useFetch,在<script setup>data仍将保持为null。结合lazy选项,这对于不需要在首次渲染时获取的数据非常有用。

/* 此调用在激活前执行 */
const articles = await useFetch('/api/article')

/* 此调用仅在客户端执行 */
const { status, data: comments } = useFetch('/api/comments', {
  lazy: true,
  server: false
})

useFetch组合式API旨在在setup方法中调用或直接在生命周期钩子的函数顶层调用,否则应该用$fetch方法。

最小化有效载荷大小

pick选项帮助通过只选择需要从组合式API返回的字段来最小化存储在HTML文档中的有效载荷大小。

<script setup lang="ts">
/* 只选择模版中使用的字段 */
const {} = await useFetch('/api/mountains/everest', {
  pick: ['title', 'description']
})
</script>

<template>
  <h1>{{ mountain.title }}</h1>
  <p>{{ mountain.description }}</p>
</template>

如果需要更多控制或映射多个对象,可以用transform函数去更改查询结果。

const { data: mountains } = await useFetch('/api/mountains', {
  transform: () => {
    return mountains.map(mountain => ({ title: mountain.title, description: mountain.description }))
  }
})

picktransform都不能阻止最初获取不需要的数据。但它们会阻止不需要的数据被添加到从服务器传输到客户端的有效载荷中。

缓存和重新获取

键(Keys)

useFetchuseAsyncData使用键来防止重新获取相同的数据。

  • useFetch使用提供的URL作为键。或者,可以在座位最后一个参数传递的options对象中提供key值。
  • useAsyncData如果第一个参数是字符串,则使用它作为键。如果第一个参数是执行查询的处理函数,则会为你生成一个对于useAsyncData实例的文件名和行号唯一的键。

要通过键获取缓存的数据,可以用useNuxtData

共享状态和选项一致性

当多个组件用相同的键调用useAsyncDatauseFetch时,它们将共享相同的dataerrorstatusref。这确保了组件之间的一致性,但需要某些选项保持一致。

  • handler函数
  • deep选项
  • transform函数
  • pick数组
  • getCachedData函数
  • default
// ❌ 这将触发开发警告
const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { deep: false })

const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { deep: true })

以下选项可以安全地不同而不会触发警告:

  • server
  • lazy
  • immediate
  • dedupe
  • watch
// 这是可以的
const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { immediate: true })

const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { immediate: false }) 

如果你需要独立的实例,请使用不同的键:

// 完全独立的实例
const { data: users1 } = useAsyncData('users-1', () => $fetch('/api/users'))
const { data: users2 } = useAsyncData('users-2', () => $fetch('/api/users'))

响应式键(Reactive Keys

可以用计算属性ref、普通refgetter函数作为键,允许动态数据获取,当依赖项更改时自动更新:

// 使用计算属性作为键
const userId = ref('123')
const { data: user } = useAsyncData(
    computed(() => `user-${userId.value}`),
    () => fetchUser(userId.value)
)

// 当 userId 更改时,数据将自动重新获取
// 并且如果没有其他组件使用旧数据,旧数据将被清理
userId.value = '456'

刷新和执行

要手动获取或刷新数据,用组合式api提供的executerefresh函数。

<script setup lang="ts">
const { data, error, execute, refresh } = await useFetch('/api/users')
</script>

<template>
  <div>
    <p>{{ data }}</p>
    <button @click="() => refresh()">刷新数据</button>
  </div>
</template>

execute函数是refresh的别名,工作方式完全相同。

清除

如果出于任何原因要清除提供的数据,不需要知道传递给clearNuxtData的特定键,可以用组合式API提供的clear函数。

<script setup lang="ts">
const { data, clear } = await useFetch('/api/users') 

const route = useRoute()
watch(() => route.path, (path) => {
  if (path === '/') clear()
})
</script>

监听

要在应用程序中的其他响应式更改时重新运行你的获取函数,用watch,监听一个或者多个。

<script setup lang="ts">
const id = ref(1)

const { data, error, refresh } = await useFetch('/api/users', {
  /* 更改id将触发重新获取 */
  watch: [id]
})
</script>

监听响应式不会改获取的URL。将继续用相同的初始用户id,因为url是在函数调用时构造的。

<script setup lang="ts">
const id = ref(1)

const { data, error, refresh } = await useFetch(`/api/users/${id.value}`, {
  watch: [id]
})
</script>

可以用计算URL

计算URL

需要URL响应式值计算,并在这些值更改时刷新数据。只需要将每个参数作为响应式值附加。Nuxt将自动用响应式值,并在每次更改时重新获取。

<script setup lang="ts">
const id = ref(null)

const { data, status } = useLazyFetch('/api/user', {
  query: {
    user_id: id
  }
})
</script>

在更复杂的URL构造情况下,可以用作为计算getter的回到函数,返回URL字符串。

每次依赖项更改时,将用新构造的URL获取数据。

结合非立即执行,可以等待响应式元素更改后再获取。

<script setup lang="ts">
const id = ref(null)

const { data, status } = useLazyFetch(() => `/api/users/${id.value}`, {
  immediate: false
})

const pending = computed(() => status.value === 'pending')
</script>

<template>
  <div>
    <!-- fetching 时禁用输入 -->
    <input v-model="id" type="number" :disabled="pending" />
    
    <div v-if="status === 'idle'">
      输入用户ID
    </div>
    
    <div v-else-if="pending">
      加载中...
    </div>
    
    <div v-else>
      {{ data }}
    </div>
  </div>
</template>

在其他响应值更改时强制刷新,可以监听其他值

非立即执行(Not immediate)

useFetch组合式API在调用时立即开始获取数据。可以通过设置immediate:false去阻止这种情况,例如,等待用户交互。这样,将需要status去处理获取生命周期,以及execute去启动数据获取。

<script setup lang="ts">
const { data, error, execute, status } = await useLazyFetch('', {
  immediate: false
})
</script>

<template>
  <div v-if="status === 'idle'">
    <button @click="execute">获取数据</button>
  </div>
  
  <div v-else-if="status === 'pending'">
    加载评论中...
  </div>
  
  <div v-else>
    {{ data }}
  </div>
</template>

为了更精细的控制,status变量可以是:

  • idle:当获取尚未开始时
  • pending:当获取已开始但尚未完成时
  • error:当获取失败时
  • success:当获取成功完成时

传递标头和Cookie

当我们在浏览器中调用$fetch时,用户标头(如 cookie)将直接发送到API。通常,在服务器端渲染期间,出于安全考虑,$fetch不会包含用户的浏览器Cookie,也不会传递来自fetch响应的Cookie。然而,当在服务器上调用useFetch并使用相对URL时,Nuxt将使用useRequestFetch去代理标头和Cookie

在SSR响应中从服务端API调用传递Cookie

要在另一个方向传递/代理Cookie,从内部请求回到客户端,要自己处理。

// composables/fetch.ts
import { appendResponseHeader } from 'h3'
import type { H3Event } from 'h3'

export const fetchWithCookie = async (event: H3Event, url: string) => {
  const res = await $fetch.raw(url)
  const cookies = res.headers.getSetCookie()
  for (const cookie of cookies) {
    appendResponseHeader(event, 'set-cookie', cookie)
  }
  /* 返回响应的数据 */
  return res._data
}
<script setup lang="ts">
// 这个组合式API将自动将Cookie传递给客户端
const event = useRequestEvent()

const {} = await useAsyncData(() => fetchWithCookie(event!, '/api/with-cookie'))

onMounted(() => console.log(document.cookie))
</script>

选项API支持

Nuxt提供了在选项API中执行asyncData获取的方法。为此,你必须将组件定义包装在defineNuxtComponent中。

<script>
export default defineNuxtComponent({
  fetchKey: 'hello',
  async asyncData () {
    return {
      hello: await $fetch('/api/hello')
    }
  }
})
</script>

<script setup><script setup lang="ts">是在Nuxt中声明Vue组件的推荐方式。

从服务器到客户端的数据序列化

当使用useAsyncDatauseLazyAsyncData将在服务器上获取的数据传输到客户端时(以及任何其他利用Nuxt有效载荷的内容),有效载荷用devalue进行序列化。

这允许我们不仅传输基本JSON,还可以序列化和恢复/反序列更高级的数据类型,如正则日期MapSetrefreactiveshallowRefshallowReactive

从API路由序列化数据

// server/api/foo.ts
export default defineEventHandler(() => {
  return new Date()
})
// app.vue
<script setup lang="ts">
// `data`的类型被推断为 string,尽管我们返回了一个Date对象
const { data } = await useFetch('/api/foo')
</script>

自定义序列化函数

// server/api/bar.ts
export default defineEventHandler(() => {
  const data = {
    createAt: new Date(),
    
    toJSON() {
      return {
        createdAt: {
          year: this.createdAt.getFullYear(),
          month: this.createAt.getMonth(),
          day: this.createdAt.getDate(),
        }
      }
    }
  }
  return data
})
// app.vue
<script setup lang="ts">
/**
`data`的类型被推断为
{
  createdAt: {
    year: number
    month: number
    day: number
  }
}
*/
const { data } = await useFetch('/api/bar')
</script>

用替代序列化器

toJSON方法去保持类型安全。

// server/api/superjson.ts

export default defineEventHandler(() => {
  const data = {
    createAt: new Date(),
    
    // 解决类型转换问题
    toJSON() {
      return this
    }
  }
  
  // 
  return superjson.stringify(data) as unknown as typeof data
})
// app.vue
<script setup lang="ts">

// `date`被推断为 { createAt: Date }, 可以安全地使用Date对象方法 
const { data } = await useFetch('/api/superjson', {
  transform: (value) => {
    return superjson.parse(value as unknown as string)
  }
})
</script>

例子

通过POST请求消费SSE 服务器发送事件

通过GET请求消费SSE,可以用EventSource或VueUse组合式APIuseEventSource

const response = await $fetch<ReadableStream>('/chats/ask-ai', {
  method: 'POST',
  body: {
    query: '你好AI,你好吗?'
  },
  responseType: 'stream'
})

// 用TextDecoderStream 从响应创建新的ReadableStream,
const reader = response.pipeThrough(new TextDecoderStream()).getReader()

while(true) {
  const { value, done } = await reader.read()
  
  if(done)
    break
    
  console.log('收到:', value)
}

并行请求

不互相依赖的接口可用这个。提高性能。

const { data } = await useAsyncData(() => {
  return Promise.all([
    $fetch("/api/comments/"),
    $fetch("/api/author/12")
  ]);
});

const comments = computed(() => data.value?.[0]);
const author = computed(() => data.value?.[1]);
❌
❌