Nuxt4阶段六:工程化与进阶 ------ 模块、中间件、插件、TS 与测试
学习目标 :会用 Nuxt Modules、路由中间件、插件;理解 Nuxt 4 TypeScript 分层;能写基础测试与统一错误处理。
预计时间 :1--2 周
前置 :完成阶段一至五。
本阶段产出:带登录守卫的后台、一个实用插件、基础测试用例。
1. Nuxt Modules:扩展能力的官方方式
模块能改构建、加目录约定、注册组件与 composables。
1.1 安装与注册
bash
pnpm add @nuxt/ui
# 或 Tailwind:pnpm add @nuxtjs/tailwindcss
pnpm add @vueuse/nuxt
pnpm add @nuxtjs/color-mode
ts
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@nuxt/ui',
'@vueuse/nuxt',
'@nuxtjs/color-mode',
],
colorMode: {
classSuffix: '',
},
})
1.2 初学者建议组合
| 模块 | 用途 |
|---|---|
@nuxt/ui 或 Tailwind |
UI / 样式 |
@vueuse/nuxt |
超全的 composables |
@nuxtjs/color-mode |
亮暗色 |
@pinia/nuxt |
状态(阶段三) |
@nuxt/image |
图片(阶段五) |
@nuxt/test-utils |
测试 |
@nuxtjs/i18n |
国际化(阶段八可用) |
不必一次装全,按需添加。
2. 路由中间件(页面级鉴权)
放在 app/middleware/。
2.1 匿名中间件(局部使用)
app/middleware/auth.ts:
ts
export default defineNuxtRouteMiddleware(async (to) => {
const { data: user } = await useFetch('/api/auth/me', {
key: 'auth-me',
// 避免重复导航时刷屏错误,可按需调整
})
if (!user.value) {
return navigateTo({
path: '/login',
query: { redirect: to.fullPath },
})
}
})
页面启用:
ts
definePageMeta({
middleware: 'auth',
layout: 'admin',
})
2.2 全局中间件
文件名加 .global.ts,例如 app/middleware/analytics.global.ts:
ts
export default defineNuxtRouteMiddleware((to) => {
if (import.meta.client) {
console.log('[nav]', to.fullPath)
}
})
每个路由都会跑------保持逻辑轻量。
2.3 中间件执行顺序
- 全局中间件(文件名排序)
- 布局/页面定义的中间件
- 页面渲染
2.4 登录页示例
app/pages/login.vue:
html
<script setup lang="ts">
definePageMeta({ layout: false })
const route = useRoute()
const username = ref('admin')
const password = ref('123456')
const errorMsg = ref('')
async function onSubmit() {
errorMsg.value = ''
try {
await $fetch('/api/auth/login', {
method: 'POST',
body: { username: username.value, password: password.value },
})
await navigateTo((route.query.redirect as string) || '/admin')
} catch (e: any) {
errorMsg.value = e?.data?.message || '登录失败'
}
}
</script>
<template>
<form @submit.prevent="onSubmit">
<h1>登录</h1>
<input v-model="username" placeholder="用户名" />
<input v-model="password" type="password" placeholder="密码" />
<p v-if="errorMsg">{{ errorMsg }}</p>
<button type="submit">登录</button>
</form>
</template>
/admin 加 middleware: 'auth',未登录会被踢回 /login。
3. 插件 Plugins
用于初始化第三方库、注入全局方法。
3.1 客户端插件
app/plugins/hello.client.ts(仅浏览器):
ts
export default defineNuxtPlugin(() => {
return {
provide: {
hello: (name: string) => `Hello, ${name}!`,
},
}
})
使用:
ts
const { $hello } = useNuxtApp()
console.log($hello('Nuxt'))
3.2 服务端插件
app/plugins/server-init.server.ts:仅 SSR/服务端运行。
3.3 通用插件
无 .client / .server 后缀则两端都跑------注意不要碰 window。
3.4 Vue 插件示例(假想)
ts
export default defineNuxtPlugin((nuxtApp) => {
// nuxtApp.vueApp.use(SomeVuePlugin)
})
排序可用文件名数字前缀:01.foo.ts、02.bar.ts。
4. TypeScript 与 Nuxt 4 项目分离
Nuxt 4 为不同上下文准备了更清晰的类型边界:
| 上下文 | 大致范围 |
|---|---|
| app | 页面、组件、composables、插件 |
| server | server/** |
| shared | shared/** |
| node / builder | 配置与构建相关 |
好处:不会在前端代码里「误用」仅服务端 API 还不报错(或反过来)。
实践建议:
- 公共类型放
shared/或shared/types - 为
runtimeConfig补类型(可通过配置自动推导,或扩展接口) - 打开严格模式,逐步修类型错误
实验性 Typed Routes :让 navigateTo('/blog/1') 等有路径提示。可在文档中开启 experimental.typedPages(以当前版本文档为准)。
5. 错误处理工程化
5.1 全局 error.vue
见阶段五;确保存在并样式可读。
5.2 组件内
ts
const { error } = await useFetch('/api/posts')
5.3 主动抛错
ts
throw createError({ statusCode: 403, statusMessage: 'Forbidden' })
5.4 钩子(了解)
ts
// plugins
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.hook('vue:error', (err) => {
console.error('vue:error', err)
})
})
6. 测试入门
bash
pnpm add -D @nuxt/test-utils vitest @vue/test-utils happy-dom
nuxt.config.ts 可按 @nuxt/test-utils 文档添加配置。示例 vitest.config.ts:
ts
import { defineVitestConfig } from '@nuxt/test-utils/config'
export default defineVitestConfig({
test: {
environment: 'nuxt',
},
})
组件测试示例:
ts
// tests/PostCard.spec.ts
import { describe, it, expect } from 'vitest'
import { mountSuspended } from '@nuxt/test-utils/runtime'
import BlogPostCard from '~/components/blog/PostCard.vue'
describe('BlogPostCard', () => {
it('renders title', async () => {
const wrapper = await mountSuspended(BlogPostCard, {
props: { id: '1', title: 'Hello', excerpt: 'World' },
})
expect(wrapper.text()).toContain('Hello')
})
})
API 测试可对 handler 做单元测试,或用 $fetch 打 setup 后的测试服务器(见官方 Testing 指南)。
package.json:
json
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest"
}
}
7. 代码组织最佳实践(中型项目)
text
app/
components/
blog/ # 按领域分
ui/ # 通用 UI
composables/
useAuth.ts
useApi.ts
middleware/
pages/
layouts/
plugins/
server/
api/
auth/
posts/
todos/
utils/
middleware/
shared/
types/
utils/ # 纯函数、无 Node/浏览器依赖
原则:
- 页面保持瘦:数据获取 + 拼装组件
- 可复用逻辑进 composables
- 可复用 UI 进 components
- 服务端 IO 不进 composables(除非包
$fetch)
8. 环境与质量工具(可选但推荐)
bash
pnpm add -D eslint @nuxt/eslint
# 按模块文档初始化
Prettier、Husky、lint-staged 可按团队习惯加。学习阶段有 ESLint 即可。
9. 暗色模式小练习
若装了 @nuxtjs/color-mode:
html
<script setup lang="ts">
const colorMode = useColorMode()
</script>
<template>
<button @click="colorMode.preference = colorMode.value === 'dark' ? 'light' : 'dark'">
当前:{{ colorMode.value }}
</button>
</template>
CSS:
css
:root { color-scheme: light; }
.dark {
color-scheme: dark;
background: #111;
color: #eee;
}
10. 本阶段验收清单
- 至少安装并配置 2 个 Nuxt 模块
-
auth中间件保护/admin,未登录跳转/login - 写过一个
.client插件并provide方法 - 存在友好的
error.vue - (加分)Vitest + 至少一个组件测试通过
- (加分)
shared/types被 app 与 server 同时引用 - (加分)接入 color-mode
11. 常见坑
中间件里错误使用 useFetch 导致循环
登录页不要挂 auth 中间件;失败时注意别重定向到自己。
插件里直接碰 window
用 .client 后缀或 import.meta.client 判断。
测试环境与运行时不一致
优先用官方 @nuxt/test-utils 的 environment: 'nuxt'。
全局中间件太重
每个导航都跑,别在里面狂打 API。
12. 阶段总结
你已经掌握:
- Modules 扩展方式
- 路由中间件鉴权流
- 插件注入与客户端/服务端拆分
- TS 分层意识、测试与错误页
下一篇:阶段七 ------ 构建、环境变量、Vercel/Docker/Node 部署与简易 CI。