改造思路
将先前的 Vite + Vue 3 首页项目迁移到 Nuxt.js 框架。前面的课程中集成的各种方案(自动组件导入、Pinia、UnoCSS、PWA 等)为这次改造做了充分铺垫,Nuxt 内置了许多等价功能,使得迁移可以复用大量已有代码。
可直接复用的部分
| 原项目模块 | Nuxt 对应方案 | 迁移方式 |
|---|---|---|
src/components/ | components/ | 直接粘贴,自动导入 |
src/hooks/ | composables/ | 粘贴后无需 index.ts,自动导入 |
src/layouts/ | layouts/ | 粘贴后使用 <NuxtLayout> 引用 |
src/pages/ | pages/ | 粘贴后使用 <NuxtPage> 替换 <RouterView> |
src/assets/ | public/ | 移至 public 目录,直接用 / 引用 |
src/stores/ | stores/ | 粘贴后用 ~/stores 路径引用 |
迁移步骤
1. 静态资源迁移
将原项目 assets/ 目录中的图片、图标等资源移至 Nuxt 的 public/ 目录:
cp -r src/assets/* public/assets/
bash
然后在代码中将引用路径从 @/assets/ 替换为 /assets/:
// 原先
import logo from '@/assets/images/logo.png'
// Nuxt 中直接使用路径
// <img src="/assets/images/logo.png" />
typescript
如果需要在构建时处理资源,也可以在 nuxt.config.ts 中配置 alias:
export default defineNuxtConfig({
alias: {
'@/assets': './public/assets',
}
})
typescript
2. Layouts 改造
Nuxt 的 layouts/ 目录会被自动识别为布局组件。
<!-- layouts/default.vue -->
<template>
<div>
<AppHeader />
<slot />
<AppFooter />
</div>
</template>
vue
在 app.vue 中指定布局:
<!-- app.vue -->
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</template>
vue
3. RouterView 替换为 NuxtPage
<!-- 原先 -->
<RouterView />
<!-- Nuxt 中 -->
<NuxtPage />
vue
4. Pages 迁移
将原项目的页面文件粘贴到 pages/ 目录,需要做以下调整:
- 移除
<RouterView />相关代码 - 将
@/stores路径替换为~/stores - 移除手动 import(Nuxt 自动导入机制会处理)
5. 404 页面
Nuxt 中 404 页面通过特殊文件名 [...slug].vue 实现:
<!-- pages/[...slug].vue -->
<script setup>
definePageMeta({
layout: '404',
})
</script>
<template>
<div class="not-found">
<h1>404</h1>
<p>没有找到页面或资源</p>
<NuxtLink to="/">返回首页</NuxtLink>
</div>
</template>
vue
definePageMeta 用于设置页面级别的元信息,如指定使用的布局。
6. PWA 注册方式
Nuxt 中 PWA 的注册方式与 Vite 项目不同:
// 原先(Vite)
import registerSW from 'virtual:register-sw'
// Nuxt 中
const { $pwa } = useNuxtApp()
// 安装 PWA
$pwa.install()
// 获取 Service Worker Registration
const registration = $pwa.getSWRegistration()
if (registration) {
setInterval(() => registration.update(), 3600 * 1000)
}
typescript
Nuxt Server API
创建 API 接口
// server/api/home.get.ts
export default defineEventHandler(() => {
return {
code: 200,
data: {
swipers: [],
swiperProjects: [],
courses: [],
}
}
})
typescript
使用 useFetch 调用 API
Nuxt 提供了 useFetch 组合式函数,用于在 SSR 和客户端统一获取数据:
const { status, data } = await useFetch('/api/home')
if (status.value === 'success' && data.value) {
swipers.value = data.value.swipers
courses.value = data.value.courses
}
typescript
useFetch 返回的 status 类型:
| 状态 | 说明 |
|---|---|
idle | 空闲,未发起请求 |
pending | 请求进行中 |
success | 请求成功 |
error | 请求失败 |
注意:
status和data都是Ref类型,需要通过.value访问实际值。
SSR 中的 document 问题
问题描述
SSR 过程中,如果代码访问了浏览器特有的 API(如 document、window),服务端会抛出 "document is not defined" 错误,因为 Node.js 环境中没有这些对象。
解决方案
方案一:使用 process.client 判断
if (process.client) {
// 仅在浏览器端执行
document.body.style.fontSize = '16px'
}
typescript
方案二:使用 onBeforeMount 钩子
onBeforeMount 和 onMounted 只在客户端执行,不会在服务端运行:
onBeforeMount(() => {
// 安全使用 document
const height = document.body.clientHeight
windowSize.value = { width: window.innerWidth, height }
})
typescript
方案三:使用 useFetch 替代 onBeforeMount 数据请求
如果需要在页面渲染前获取异步数据,应该使用 useFetch 而非 onBeforeMount:
// 不推荐:onBeforeMount 在 SSR 时不会执行数据获取
onBeforeMount(async () => {
const res = await fetch('/api/home')
data.value = await res.json()
})
// 推荐:useFetch 在 SSR 和客户端都能正确工作
const { data } = await useFetch('/api/home')
typescript
调试技巧
遇到 "document is not defined" 错误时的排查步骤:
- 根据错误提示定位到具体的 Layout 或组件文件
- 在文件中搜索
document关键字 - 将涉及浏览器 API 的代码包裹在
process.client判断中 - 如果步骤 3 无法快速定位,可以分区块注释代码,逐步缩小范围
路径别名重要提醒
Nuxt 中的路径别名与 Vue 项目存在差异:
| 用途 | Vue 项目 | Nuxt 项目 |
|---|---|---|
| Store 引用 | @/stores/useXxx | ~/stores/useXxx |
| 组件引用 | @/components/Xxx | 直接使用组件名(自动导入) |
| 资源引用 | @/assets/xxx | /assets/xxx(public 目录) |
迁移时务必全局替换路径引用,避免因路径不一致导致的导入错误。
↑