懒加载获取
默认情况下,useFetch 和 useAsyncData 会阻塞导航直到数据加载完成。懒加载模式下,数据在后台获取,不阻塞导航——页面立刻显示,数据加载完后自动更新。
为什么需要懒加载?
| 问题 | 阻塞模式(默认) | 懒加载模式 |
|---|---|---|
| 页面切换慢 | 等所有数据加载完才显示 | 页面立刻显示 |
| 非关键数据拖慢首屏 | 评论区、推荐等阻塞渲染 | 先显示页面,后台加载 |
| 用户感知差 | 点击链接后白屏等待 | 立刻看到页面框架 + Loading |
| SEO 影响 | 数据完整,SEO 好 | 数据不完整,SEO 差 |
核心取舍
懒加载 = 更快的页面切换 换取 SEO 和首屏数据完整性。
经验法则
页面核心内容(文章、商品)用阻塞加载,非关键内容(评论、推荐、侧边栏)用懒加载。
useLazyFetch
useLazyFetch 是 useFetch 的懒加载版本:
<script setup>
const { data, pending, error } = useLazyFetch('/api/users')
</script>
<template>
<div v-if="pending">加载中...</div>
<div v-else-if="error">{{ error.message }}</div>
<div v-else>
<ul>
<li v-for="user in data" :key="user.id">{{ user.name }}</li>
</ul>
</div>
</template>等价于:
const { data, pending, error } = await useFetch('/api/users', { lazy: true })useLazyFetch vs useFetch({ lazy: true })
两者完全等价。useLazyFetch 是语法糖,语义更明确。推荐用 useLazyFetch,因为:
- 代码意图更清晰——一眼就知道是懒加载
- 不需要
await(懒加载不阻塞导航)
INFO
️ 注意:useLazyFetch 不需要 await 因为它不阻塞导航。加了 await 也没效果
useLazyAsyncData
useLazyAsyncData 是 useAsyncData 的懒加载版本:
<script setup>
const { data, pending, error } = useLazyAsyncData(
'users',
() => $fetch('/api/users')
)
</script>
<template>
<div v-if="pending">加载中...</div>
<div v-else>
<ul>
<li v-for="user in data" :key="user.id">{{ user.name }}</li>
</ul>
</div>
</template>等价于:
const { data, pending } = await useAsyncData('users', () => $fetch('/api/users'), { lazy: true })什么时候用 useLazyAsyncData?
当你的获取逻辑不是简单的 HTTP 请求时(如聚合多个 API、使用第三方 SDK),用 useLazyAsyncData 包裹并启用懒加载。
阻塞 vs 非阻塞——深入理解
阻塞模式(默认)
用户点击链接 → 等待数据加载 → 页面显示
↑ 阻塞在这里
│ 服务端:等待 useFetch 完成 → 渲染完整 HTML
│ 客户端导航:显示 NuxtLoadingIndicator → 等待数据- 页面切换时有等待
- 数据加载完才显示页面
- SEO 友好(SSR 时数据已就绪,搜索引擎能抓到)
- 用户体验:等待 → 完整内容(一气呵成)
非阻塞模式(lazy)
用户点击链接 → 页面立即显示 → 数据加载完成
↑ 显示 loading
│ SSR 时:HTML 中不包含数据
│ 客户端导航:立刻显示页面框架- 页面切换立刻完成
- 先显示 loading 状态,数据加载后自动更新
- SEO 不友好(SSR 时数据未就绪,搜索引擎抓不到)
- 用户体验:立刻看到页面 → loading → 内容(渐进式)
SSR 时懒加载的数据去哪了?
- 阻塞模式:服务端等待数据 → 嵌入 HTML → 客户端直接使用
- 懒加载模式:服务端不等待 → HTML 中无数据 → 客户端 Hydration 后自行请求
这就是懒加载"SEO 不友好"的原因——搜索引擎抓到的 HTML 里没有数据。
何时使用懒加载
✅ 适合懒加载
| 场景 | 原因 | 示例 |
|---|---|---|
| 非关键数据 | 不影响页面主体内容 | 评论区、推荐列表 |
| 用户交互后加载 | 需要用户操作才触发 | 折叠面板内容、Tab 切换内容 |
| 不影响 SEO | 后台页面、需登录页面 | 用户设置页、管理后台 |
| 加载时间长的数据 | 避免阻塞整个页面 | 大型报表、统计图表 |
| 个性化数据 | 每个用户不同,不适合 SSR | "为你推荐"、最近浏览 |
❌ 不适合懒加载
| 场景 | 原因 | 应该用 |
|---|---|---|
| 页面核心内容 | 没有 SEO 数据搜索引擎不收录 | useFetch(阻塞) |
| SEO 重要的数据 | 需要搜索引擎抓取 | useFetch(阻塞) |
| 页面结构依赖的数据 | 没有数据页面会变形 | useFetch(阻塞) |
| 需要服务端鉴权的数据 | 需要服务端验证身份 | useFetch + server: true |
设置默认值
懒加载时 data 初始为 undefined,使用 default 设置默认值避免模板报错:
// ❌ 不设 default:data 初始为 undefined
const { data } = useLazyFetch('/api/users')
// 模板中 v-for="user in data" → 报错(undefined 没有 .map)// ✅ 设置 default:data 初始为空数组
const { data } = useLazyFetch('/api/users', {
default: () => [], // data 初始为 [],v-for 不报错
})default 的常见值
- 列表数据:
default: () => [] - 单个对象:
default: () => ({}) - 计数等数字:
default: () => 0 - 字符串:
default: () => '' - 布尔值:
default: () => false
INFO
️ default 必须是函数:default: () => [] ✅ default: [] ❌。因为函数每次调用返回新引用,避免多个组件共享同一引用
与 ClientOnly 配合
对于完全不需要 SSR 的数据,可以配合 <ClientOnly> 使用:
<template>
<!-- 核心内容:阻塞加载 -->
<article>
<h1>{{ article.title }}</h1>
<div v-html="article.content" />
</article>
<!-- 评论区:仅客户端渲染 + 懒加载 -->
<ClientOnly>
<LazyComments :article-id="article.id" />
<template #fallback>
<p>评论加载中...</p>
</template>
</ClientOnly>
</template>ClientOnly + 懒加载 vs 单独懒加载
- 单独
useLazyFetch:SSR 时仍会执行请求(只是不阻塞),HTML 中可能有空占位 ClientOnly:SSR 时完全不渲染组件,连请求都不发。适合依赖浏览器 API 的组件(如图表、地图)
完整示例——文章页
<script setup>
const route = useRoute()
const id = route.params.id
// 核心数据:阻塞加载(SEO 重要)
const { data: article } = await useFetch(`/api/articles/${id}`)
// 评论区:懒加载(非关键,SEO 不需要)
const { data: comments, pending: commentsLoading } = useLazyFetch(
`/api/articles/${id}/comments`,
{ default: () => [] }
)
// 相关推荐:懒加载(非关键)
const { data: related, pending: relatedLoading } = useLazyFetch(
`/api/articles/${id}/related`,
{ default: () => [] }
)
// 阅读统计:仅客户端(依赖浏览器 API)
const { data: stats } = useLazyFetch(`/api/articles/${id}/stats`, {
server: false,
default: () => ({ views: 0, likes: 0 }),
})
</script>
<template>
<article>
<h1>{{ article.title }}</h1>
<div v-html="article.content" />
<p>阅读 {{ stats.views }} · 点赞 {{ stats.likes }}</p>
</article>
<!-- 评论区 -->
<section>
<h2>评论</h2>
<div v-if="commentsLoading">加载评论中...</div>
<div v-else>
<Comment v-for="c in comments" :key="c.id" :comment="c" />
</div>
</section>
<!-- 相关推荐 -->
<section>
<h2>相关文章</h2>
<div v-if="relatedLoading">加载推荐中...</div>
<div v-else>
<ArticleCard v-for="r in related" :key="r.id" :article="r" />
</div>
</section>
</template>这个示例展示了最佳实践
- 文章正文用
useFetch(阻塞)→ SEO + 首屏完整 - 评论和推荐用
useLazyFetch(懒加载)→ 不阻塞页面 - 阅读统计用
server: false→ 仅客户端,避免 SSR 无意义请求 - 所有懒加载都设了
default→ 避免 undefined 报错
懒加载组件
Nuxt 还支持组件级别的懒加载——只在组件首次使用时才加载代码:
<template>
<!-- 懒加载组件:只在首次渲染时加载 -->
<LazyHeavyChart :data="chartData" />
<!-- 普通组件:页面加载时就下载代码 -->
<SimpleCard>...</SimpleCard>
</template>懒加载组件 vs 懒加载数据
LazyXxx:懒加载组件代码(减少首屏 JS 体积)useLazyFetch:懒加载数据(不阻塞页面切换)- 两者可以组合使用:懒加载组件 + 懒加载数据
常见问题
1. 懒加载导致 Hydration 不匹配?
懒加载时 data 初始为 undefined,如果服务端和客户端渲染结果不同,可能出现 Hydration 不匹配。解决方案:
// ✅ 使用 default 提供一致的初始值
const { data } = useLazyFetch('/api/users', {
default: () => [],
})2. 懒加载的数据什么时候开始请求?
// useLazyFetch 在组件 setup 时就开始请求
// 只是页面不会等待请求完成
const { data, pending } = useLazyFetch('/api/users')
// pending 立刻为 true,请求在后台进行3. 懒加载和 server: false 的区别?
// 懒加载:SSR 时请求但不等待,客户端 Hydration 后使用客户端请求的结果
const { data } = useLazyFetch('/api/data')
// server: false:SSR 时不请求,只在客户端请求
const { data } = await useFetch('/api/data', { server: false })
// 组合:SSR 时不请求 + 不阻塞
const { data } = useLazyFetch('/api/data', { server: false })如何选择
- 只需要"不阻塞" →
useLazyFetch(默认server: true,SSR 时仍请求) - SSR 时完全不需要请求 →
server: false(如用户个人信息) - 两者都要 →
useLazyFetch + server: false
知识脉络
useFetch → useAsyncData → $fetch
│
└─→ 你在这里:懒加载获取
│
├─→ 相关:数据获取选项(lazy / server 详解)
│
└─→ 相关:缓存与刷新