[Lv2] SSR 实现:Data Fetching 与 SEO Meta 管理
在 Nuxt 3 项目中,实现 SSR 数据加载与 SEO Meta Tags 动态管理,确保搜索引擎能正确索引动态路由页面。
1. 面试回答主轴
- Data Fetching 策略:使用
useFetch/useAsyncData在 Server Side 预先加载数据,确保 SEO 内容完整。 - 动态 Meta Tags:使用
useHead根据数据动态生成 SEO meta tags,支持动态路由页面。 - 性能优化:实现 request deduplication、server-side caching,区分 SSR/CSR 页面。
2. useFetch / useAsyncData 的正确使用
2.1 为什么需要 SSR Data Fetching?
问题情境:
- 动态路由页面(如:
/products/[id])需要从 API 加载数据 - 如果只在客户端加载,搜索引擎无法看到完整内容
- 需要确保 Server Side 预先加载数据,生成完整的 HTML
解决方案: 使用 Nuxt 3 的 useFetch 或 useAsyncData
2.2 useFetch 基础使用
文件位置: pages/products/[id].vue
// 基本用法
const { data: product } = await useFetch(`/api/products/${route.params.id}`);
关键参数说明:
| 参数 | 说明 | 默认值 |
|---|---|---|
key | 唯一标识码,用于 request deduplication | 自动生成 |
lazy | 是否延迟加载(不阻塞 SSR) | false |
server | 是否在 Server Side 执行 | true |
default | 默认值 | null |
transform | 数据转换函数 | - |
2.3 完整实现范例
// pages/products/[id].vue
const { data: product } = await useFetch(`/api/products/${route.params.id}`, {
key: `product-${route.params.id}`, // 避免重复请求
lazy: false, // SSR 时等待完成
server: true, // 确保 server side 执行
default: () => ({
id: null,
name: '',
description: '',
image: '',
}),
transform: (data: any) => {
// 数据转换逻辑
return {
...data,
formattedPrice: formatPrice(data.price),
};
},
});
关键点说明:
-
key参数- 用于 request deduplication(避免重复请求)
- 相同 key 的请求会被合并
- 建议使用唯一标识码(如:
product-${id})
-
lazy: false- SSR 时会等待数据加载完成才渲染
- 确保搜索引擎能看到完整内容
- 如果设为
true,SSR 时不会等待,可能导致内容不完整
-
server: true- 确保在 Server Side 执行
- 这是 SSR 的关键设定
- 如果设为
false,只会在客户端执行
2.4 useAsyncData vs useFetch
差异对比:
| 功能 | useFetch | useAsyncData |
|---|---|---|
| 用途 | 直接调用 API | 执行任意异步操作 |
| 自动处理 | ✅ 自动处理 URL、headers | ❌ 需要手动处理 |
| 适用场景 | API 请求 | 数据库查询、文件读取等 |
使用范例:
// useFetch:适合 API 请求
const { data } = await useFetch('/api/products/123');
// useAsyncData:适合其他异步操作
const { data } = await useAsyncData('products', async () => {
// 可以执行任何异步操作
const result = await someAsyncOperation();
return result;
});
2.5 $fetch vs useFetch
面试常考题:什么时候该用 $fetch,什么时候该用 useFetch?
1. $fetch
- 定义:Nuxt 3 底层使用的 HTTP 客户端(基于
ofetch)。 - 行为:单纯发送 HTTP 请求,不会处理 SSR 状态同步(Hydration)。
- 风险:如果在
setup()中直接使用$fetch,Server 端会请求一次,Client 端 Hydration 时会再次请求(Double Fetch),且可能导致 Hydration Mismatch。 - 适用场景:
- 用户交互触发的请求(如:点击按钮提交表单、加载更多)。
- Client-side only 的逻辑。
- Middleware 或 Server API route 内部。
2. useFetch
- 定义:封装了
useAsyncData+$fetch的 Composable。 - 行为:
- 自动生成 key 进行 Request Deduplication。
- 处理 SSR 状态传输(将 Server 获取的数据传给 Client,避免 Client 再次请求)。
- 提供响应式返回值(
data,pending,error,refresh)。
- 适用场景:
- 页面初始化需要的数据(Page Load)。
- 依赖 URL 参数变动的数据获取。
总结比较:
| 特性 | useFetch | $fetch |
|---|---|---|
| SSR 状态同步 | ✅ 有 (Hydration Friendly) | ❌ 无 (可能 Double Fetch) |
| 响应式 (Ref) | ✅ 返回 Ref 对象 | ❌ 返回 Promise (Raw Data) |
| 主要用途 | 页面数据加载 (Data Fetching) | 事件处理、操作型请求 (Actions) |
// ⭕️ 正确:页面加载使用 useFetch
const { data } = await useFetch('/api/user');
// ⭕️ 正确:点击事件使用 $fetch
const submitForm = async () => {
await $fetch('/api/submit', { method: 'POST', body: form });
};
// ❌ 错误:在 setup 中直接使用 $fetch (导致 Double Fetch)
const data = await $fetch('/api/user');
3. SEO Meta 管理(useHead)
3.1 为什么需要动态 Meta Tags?
问题情境:
- 动态路由页面(如:产品页、文章页)需要根据数据 动态生成 Meta Tags
- 每个页面应该有独特的 title、description、og:image
- 需要支持 Open Graph、Twitter Card 等社交媒体标签
解决方案: 使用 Nuxt 3 的 useHead 或 useSeoMeta
3.2 useHead 基础使用
文件位置: pages/products/[id].vue
useHead({
title: () => product.value?.name,
meta: [
{ name: 'description', content: () => product.value?.description },
{ property: 'og:title', content: () => product.value?.name },
{ property: 'og:image', content: () => product.value?.image },
],
link: [
{
rel: 'canonical',
href: () => `https://example.com/products/${product.value?.id}`,
},
],
});
关键点说明:
-
使用函数返回值
- 使用
() => product.value?.name而非直接product.value?.name - 确保数据更新时 Meta Tags 也会更新
- 支持响应式更新
- 使用
-
完整的 SEO Meta Tags
title:页面标题meta:description、keywords、og tags 等link:canonical URL、alternate 等
3.3 useSeoMeta 简化写法
Nuxt 3 也提供了 useSeoMeta,专门用于 SEO meta tags:
useSeoMeta({
title: () => product.value?.name,
description: () => product.value?.description,
ogTitle: () => product.value?.name,
ogDescription: () => product.value?.description,
ogImage: () => product.value?.image,
twitterCard: 'summary_large_image',
});
优点:
- ✅ 语法更简洁
- ✅ 自动处理 Open Graph 和 Twitter Card
- ✅ 支持响应式更新
3.4 完整实现范例
// pages/products/[id].vue
<script setup lang="ts">
const route = useRoute();
// 1. 加载产品数据(SSR)
const { data: product } = await useFetch(`/api/products/${route.params.id}`, {
key: `product-${route.params.id}`,
lazy: false,
server: true,
});
// 2. 动态生成 SEO Meta Tags
useHead({
title: () => product.value?.name || '产品页',
meta: [
{
name: 'description',
content: () => product.value?.description || '',
},
{
property: 'og:title',
content: () => product.value?.name || '',
},
{
property: 'og:description',
content: () => product.value?.description || '',
},
{
property: 'og:image',
content: () => product.value?.image || '',
},
{
property: 'og:type',
content: 'product',
},
],
link: [
{
rel: 'canonical',
href: () => `https://example.com/products/${product.value?.id}`,
},
],
});
</script>
4. 实战 Case 1:动态路由 SEO 优化
4.1 问题背景
情境: 电商平台有 10 万+ SKU,需要确保每个产品页都能被 Google 正确索引。
挑战:
- 大量动态路由页面(
/products/[id]) - 每个页面需要独特的 SEO 内容
- 需要处理 404 情境
- 避免重复内容问题
4.2 解决方案
Step 1: 使用 useFetch 预先加载数据
// pages/products/[id].vue
const { data: product, error } = await useFetch(
`/api/products/${route.params.id}`,
{
key: `product-${route.params.id}`,
lazy: false, // SSR 时等待完成
server: true, // 确保 server side 执行
}
);
// 处理 404 情境
if (error.value || !product.value) {
throw createError({
statusCode: 404,
statusMessage: 'Product not found',
});
}
关键点:
- ✅ 使用
lazy: false确保 SSR 时等待数据加载 - ✅ 正确处理 404,返回 404 status code
- ✅ 使用
key避免重复请求
Step 2: 动态生成 Meta Tags
useHead({
title: () => `${product.value?.name} - 产品页`,
meta: [
{
name: 'description',
content: () => product.value?.description || '',
},
{
property: 'og:title',
content: () => product.value?.name || '',
},
{
property: 'og:image',
content: () => product.value?.image || '',
},
],
link: [
{
rel: 'canonical',
href: () => `https://example.com/products/${product.value?.id}`,
},
],
});
关键点:
- ✅ 每个产品页都有独特的 title、description
- ✅ 设定 canonical URL 避免重复内容
- ✅ 支持 Open Graph 标签
Step 3: 处理 404 情境
// 在 useFetch 后检查
if (error.value || !product.value) {
throw createError({
statusCode: 404,
statusMessage: 'Product not found',
});
}
// 或在 error.vue 中处理
// error.vue
<script setup lang="ts">
const props = defineProps<{
error: {
statusCode: number;
statusMessage: string;
};
}>();
// 设定 404 页面的 SEO
useHead({
title: '404 - 找不到页面',
meta: [
{
name: 'robots',
content: 'noindex, nofollow', // 告诉搜索引擎不要索引 404 页面
},
],
});
</script>
关键点:
- ✅ 正确返回 404 status code
- ✅ 404 页面设定
noindex, nofollow - ✅ 提供友好的错误信息
4.3 实现效果
优化前:
- ❌ 搜索引擎无法看到产品内容(只在客户端加载)
- ❌ 所有产品页共用相同的 Meta Tags
- ❌ 404 页面没有正确处理
优化后:
- ✅ 搜索引擎能看到完整的产品内容
- ✅ 每个产品页都有独特的 SEO Meta Tags
- ✅ 正确处理 404,避免索引错误页面
- ✅ 设定 canonical URL,避免重复内容问题