数据获取与 SEO
Nuxt 3 与 SSR 概念 · 阅读后完成下方实验与检查题,再用「标记完成」记录进度。
学习目标
SSR 的数据获取规则和 SPA 完全不同:数据要「服务端先拿到,再传给客户端」。这一节掌握 useFetch / useAsyncData 的差别、SSR 首屏数据如何避免重复请求、以及 SEO 的三件套(title、meta、结构化数据)。
问题场景
按 V12 的经验,在 onMounted 里 fetch 数据。Nuxt 页面确实能显示,但「查看源代码」里没有内容——数据是客户端拿的,SSR 白做。另一个问题:服务端拿了一遍数据,客户端 hydrate 后又拿一遍,接口被打两次。
核心解释
useFetch 是 Nuxt 对 useAsyncData + $fetch 的封装,专为 SSR 设计:服务端执行、结果序列化进 HTML、客户端复用不再请求。
<!-- pages/courses/index.vue -->
<script setup>
const { data: courses, pending, error, refresh } = await useFetch("/api/courses", {
// 可选:把响应字段裁剪后再序列化
pick: ["id", "title", "slug"],
});
</script>
<template>
<p v-if="pending">加载中…</p>
<p v-else-if="error">加载失败:{{ error.message }}</p>
<ul v-else>
<li v-for="c in courses" :key="c.id">
<NuxtLink :to="`/courses/${c.slug}`">{{ c.title }}</NuxtLink>
</li>
</ul>
<button @click="refresh()">刷新</button>
</template>
关键差异:
| API | 用途 |
|---|---|
useFetch(url) |
直接请求 URL,SSR 友好,最常用 |
useAsyncData(key, fn) |
执行任意异步函数(多个请求、SDK 调用),手动给唯一 key |
$fetch |
纯请求(不参与 SSR 数据传递),用在事件处理里 |
key 的作用:同一页面里多个 useAsyncData 用同一个 key 会复用结果;useFetch 默认按 URL 生成 key。
避免重复请求:SSR 拿了数据后,Nuxt 把结果写进 __NUXT__ 载荷,客户端 hydrate 时直接复用。所以不要在 onMounted 里再请求一遍——那正是把 SSR 的好处浪费掉。
只在客户端执行的代码:import.meta.client / process.client(Nuxt 3 用 import.meta.client):
if (import.meta.client) {
localStorage.setItem("theme", "dark");
}
SEO 三件套:useHead 设置 title/meta,useSeoMeta 是更简洁的封装:
<script setup>
const course = await $fetch(`/api/courses/${useRoute().params.slug}`);
useSeoMeta({
title: `${course.title} · 我的学习站`,
description: course.summary,
ogTitle: course.title,
ogDescription: course.summary,
ogImage: course.cover,
});
</script>
验证方式:查看源代码里应有对应的 <title> 与 <meta property="og:...">。社交平台的预览卡片就是靠这些标签。
SSR 的错误处理:useFetch 的 error 是 NuxtError,可以 createError({ statusCode: 404, statusMessage: "课程不存在" }) 抛给 Nuxt 的错误页;也可以在模板里局部处理(如上)。
示例拆解
一个带 SEO + 404 处理的课程详情页:
<!-- pages/courses/[slug].vue -->
<script setup>
const route = useRoute();
const { data: course, error } = await useFetch(`/api/courses/${route.params.slug}`);
if (error.value) {
throw createError({ statusCode: 404, statusMessage: "课程不存在" });
}
useSeoMeta({
title: `${course.value.title} · Vue 课程`,
description: course.value.summary,
});
</script>
<template>
<article>
<h1>{{ course.title }}</h1>
<p>{{ course.summary }}</p>
</article>
</template>
注意:error 判断要用 error.value(composable 返回的是 ref);course 在模板里自动解包、在 script 里用 .value。这是 Nuxt 与纯 Vue 一致的地方——SSR 没有改变响应式的用法。
验证清单:打开 /courses/v01 → 查看源代码能看到 h1 标题 → title 正确 → 访问不存在的 slug 得到 404 状态码(用 curl -I 看,而不是页面文案)。
动手任务
- 给列表页接
useFetch,核对「查看源代码有内容」与「Network 里首屏没有重复请求」。 - 给详情页加
useSeoMeta,用 curl 抓 HTML 确认 meta 存在。 - 用
useAsyncData写一个「并行请求两个接口」的页面(比如课程 + 用户进度),对比串行await的耗时。
常见错误
- 在
onMounted里请求首屏数据:SSR 拿不到,源码里没有内容。用 useFetch/useAsyncData。 - 在
<script setup>顶层用fetch:客户端才执行,服务端无内容;Nuxt 里用$fetch(内部转发到 useFetch 的上下文)或 useFetch。 - SSR 页面访问 localStorage/window:服务端没有这些 API。用
import.meta.client守卫。 useAsyncData的 key 重复:两个请求互相覆盖结果。key 保证页面内唯一。
检查与复盘
- 能说出 useFetch 与 useAsyncData 的分工
- 能解释 SSR 数据为什么不重复请求
- 能用 curl/查看源代码验证 SSR 与 SEO 标签
参考资料
- Nuxt 官方文档「数据获取」:useFetch/useAsyncData/$fetch
- Nuxt 官方文档「SEO 与 Meta」:useSeoMeta
读完这一课了?
标记完成后,工作台会安排接下来的学习。