
本文详解如何在 nuxt 3 中可靠地监听页面导航生命周期、捕获异步数据加载完成状态,并结合静态资源(如图片、第三方库)加载逻辑,实现精准的全局加载态控制。
本文详解如何在 nuxt 3 中可靠地监听页面导航生命周期、捕获异步数据加载完成状态,并结合静态资源(如图片、第三方库)加载逻辑,实现精准的全局加载态控制。
在 Nuxt 3 中实现「真正意义上的加载完成」——即等待页面路由过渡、useAsyncData/useFetch 数据请求、以及关键静态资源(如首屏图片、字体、CDN 脚本)全部就绪后再隐藏加载页——需要分层处理。仅依赖 page:start 和 page:finish 钩子是不充分的:它们仅反映 Vue 页面组件挂载流程,不保证异步数据已返回,更不感知静态资源加载状态。
✅ 正确做法:组合生命周期钩子 + 数据加载状态 + 资源预加载
1. 使用 onServerPrefetch 与 onMounted 确保数据就绪
page:finish 钩子在服务端渲染(SSR)和客户端水合后触发,但它不等待 useAsyncData 的响应。因此,应在页面级组件中显式同步数据加载状态:
<!-- pages/index.vue -->
<script setup>
const { data, pending } = useAsyncData('posts', () => $fetch('/api/posts'))
const loading = ref(pending.value) // 初始状态由 pending 决定
watch(pending, (newVal) => {
loading.value = newVal
})
</script>
<template>
<div v-if="loading" class="fixed inset-0 bg-white z-50 flex items-center justify-center">
<div class="text-xl font-medium">Loading...</div>
</div>
<div v-else>
<h1>Posts List</h1>
<pre>{{ data }}</pre>
</div>
</template>? 提示:pending 是响应式引用,会随 useAsyncData 状态自动更新,无需手动监听 page:finish。
2. 全局加载态:用 nuxtApp.hook + useState 实现跨组件共享
若需全局统一加载 UI(如顶部进度条或全屏遮罩),推荐在 app.vue 中管理,并通过 useState 持久化状态:
<!-- app.vue -->
<script setup>
const nuxtApp = useNuxtApp()
const loading = useState<boolean>('globalLoading', () => true)
// 页面开始导航时开启
nuxtApp.hook('page:start', () => {
loading.value = true
})
// 页面完成(含数据加载完毕)后关闭 —— 注意:需配合 useAsyncData 的 pending
nuxtApp.hook('page:finish', () => {
// 延迟一帧确保所有 pending watch 已响应
nextTick(() => {
// 若当前无 pending 请求,则关闭
const pendingStates = reactive({ count: 0 })
nuxtApp.hook('app:data-loaded', () => {
pendingStates.count--
if (pendingStates.count <= 0) loading.value = false
})
// 手动触发一次检查(实际项目中建议封装为 useGlobalLoading())
loading.value = false
})
})
</script>但更简洁可靠的方案是:放弃纯钩子方案,改用 useState + onBeforeMount/onMounted 统一收敛加载逻辑。
3. 静态资源加载:主动监听关键资源就绪
Nuxt 3 不自动追踪 <img> 或 <link> 加载,需手动处理。例如,预加载首屏关键图片:
<script setup>
const loading = ref(true)
const imageLoaded = ref(false)
onMounted(() => {
const img = new Image()
img.src = '/hero.jpg'
img.onload = () => {
imageLoaded.value = true
}
img.onerror = () => {
imageLoaded.value = true // 失败也视为“加载完成”,避免阻塞
}
})
// 当数据 + 图片均就绪时关闭加载
watch([() => !pending.value, () => imageLoaded.value], ([isDataReady, isImgReady]) => {
if (isDataReady && isImgReady) {
loading.value = false
}
})
</script>⚠️ 注意事项
- ❌ page:finish ≠ “所有请求完成”:它在组件 setup() 执行完即触发,此时 useAsyncData 可能仍在 pending。
- ✅ 推荐以 pending 状态为唯一可信信号,而非钩子事件。
- ? SSR 场景下,服务端已预取数据,客户端首次 pending 为 false;需用 useState 同步初始状态。
- ? 第三方 JS/CSS(如 Analytics、Font Awesome)应通过 useHead 注入,并在 onMounted 中用 document.readyState 或 window.addEventListener('load') 辅助判断(适用于非核心资源)。
✅ 最佳实践总结
| 场景 | 推荐方式 |
|---|---|
| 页面数据加载 | 监听 useAsyncData().pending 或 useFetch().pending |
| 全局加载 UI | 在 app.vue 中用 useState + watch([pending1, pending2]) |
| 关键静态资源 | new Image().onload / link.onload 主动监听 |
| 第三方脚本 | useHead({ script: [...] }) + onMounted(() => { /* check window.MyLib */ }) |
通过分层控制数据流与资源流,你将获得可预测、可调试、且 SSR 友好的加载体验——告别“闪屏”与“假完成”。










