插件模式
通过文件名后缀控制插件在服务端还是客户端执行。这是 Nuxt 的约定式配置——文件名即配置。
三种模式
| 文件名后缀 | SSR | 客户端 | 典型用途 |
|---|---|---|---|
.ts(无后缀) | ✅ | ✅ | 通用逻辑 |
.client.ts | ❌ | ✅ | 浏览器 API、统计代码 |
.server.ts | ✅ | ❌ | 服务端初始化 |
为什么需要区分执行环境?
- 客户端插件使用了
window、document、localStorage等,SSR 时不存在 - 服务端插件访问了 Node.js API、数据库等,客户端不需要
- 减少客户端打包体积——服务端代码不会发送到浏览器
选择原则
- 使用了浏览器 API?→
.client.ts - 使用了 Node.js API?→
.server.ts - 两者都没用?→
.ts(默认)
.client.ts 插件
仅在客户端执行,SSR 时完全跳过:
Google Analytics
ts
// app/plugins/analytics.client.ts
export default defineNuxtPlugin(() => {
window.dataLayer = window.dataLayer || []
const router = useRouter()
router.afterEach((to) => {
window.dataLayer.push({
event: 'page_view',
page_path: to.fullPath,
})
})
})WebSocket
ts
// app/plugins/websocket.client.ts
export default defineNuxtPlugin(() => {
const ws = new WebSocket('wss://api.example.com/ws')
ws.onmessage = (event) => {
const data = JSON.parse(event.data)
// 处理消息
}
return {
provide: {
ws,
},
}
})Local Storage
ts
// app/plugins/storage.client.ts
export default defineNuxtPlugin(() => {
return {
provide: {
storage: {
get: (key: string) => localStorage.getItem(key),
set: (key: string, value: string) => localStorage.setItem(key, value),
remove: (key: string) => localStorage.removeItem(key),
},
},
}
}).client.ts 的常见场景
- 统计代码(GA、百度统计、Sentry)
- WebSocket 连接
- Local Storage / Session Storage
- 第三方客户端 SDK(如 Stripe.js、Intercom)
- 使用
canvas、WebGL的库
.server.ts 插件
仅在服务端执行,客户端打包时完全不包含:
初始数据预加载
ts
// app/plugins/init-data.server.ts
export default defineNuxtPlugin(() => {
const config = useState('app-config', () => ({
loaded: true,
version: '1.0.0',
}))
// 在 SSR 时预加载配置到 useState
// 客户端 Hydration 时自动获取
})服务端日志
ts
// app/plugins/logger.server.ts
export default defineNuxtPlugin(() => {
// 只在服务端记录访问日志
const nuxtApp = useNuxtApp()
nuxtApp.hook('page:finish', () => {
// 记录服务端访问日志
logToServer({
url: nuxtApp.ssrContext?.url,
timestamp: new Date().toISOString(),
})
})
}).server.ts 的常见场景
- 服务端数据预加载
- 服务端日志
- SSR 专属逻辑
- 使用 Node.js API 的初始化
INFO
️ 注意:.server.ts 插件中 provide 的数据 客户端无法访问(不会打包到客户端)
默认模式(无后缀)
两端都执行,适合不依赖特定环境的通用逻辑:
ts
// app/plugins/init.ts
export default defineNuxtPlugin(() => {
// SSR 和客户端都执行
console.log('Plugin loaded')
// 使用 import.meta.client / import.meta.server 判断环境
if (import.meta.client) {
console.log('客户端环境')
}
if (import.meta.server) {
console.log('服务端环境')
}
})import.meta.client / import.meta.server
在代码中判断当前运行环境。比文件名后缀更灵活,适合同一个插件中部分逻辑只在特定环境执行的场景。
环境判断对比
ts
// 方式 1:文件名后缀(推荐,更清晰)
// app/plugins/analytics.client.ts
export default defineNuxtPlugin(() => {
window.gtag(...) // 安全:这个文件只在客户端执行
})
// 方式 2:代码中判断(灵活,但不够清晰)
// app/plugins/analytics.ts
export default defineNuxtPlugin(() => {
if (import.meta.client) {
window.gtag(...) // 只在客户端执行
}
})推荐用文件名后缀
一眼就能看出插件的执行环境,不需要阅读代码。
实战——认证插件组合
ts
// app/plugins/01.auth.ts —— 通用认证逻辑(两端执行)
export default defineNuxtPlugin(() => {
const user = useState('user', () => null)
return {
provide: {
auth: {
user,
isAuthenticated: computed(() => !!user.value),
},
},
}
})ts
// app/plugins/02.auth-client.client.ts —— 客户端专属认证逻辑
export default defineNuxtPlugin(() => {
const { $auth } = useNuxtApp()
const router = useRouter()
// 监听 token 变化,自动跳转
watch(() => $auth.isAuthenticated, (val) => {
if (!val) {
router.push('/login')
}
})
// 初始化时检查 token
const token = useCookie('auth-token')
if (!token.value) {
$auth.user.value = null
}
})ts
// app/plugins/03.auth-server.server.ts —— 服务端专属认证逻辑
export default defineNuxtPlugin(() => {
// 在 SSR 时从 cookie 恢复用户信息
const nuxtApp = useNuxtApp()
nuxtApp.hook('app:created', () => {
const token = useCookie('auth-token')
if (token.value) {
// 验证 token 并设置用户信息
// 注意:不能在这里发异步请求
}
})
})注意事项
- 文件名严格:必须是
.client.ts或.server.ts,不支持.browser.ts等其他命名 - 避免状态不一致:两端都执行的插件要确保状态在 SSR 和客户端之间一致
- ClientOnly 替代:简单的客户端逻辑可以用
<ClientOnly>而非客户端插件 - 打包体积:
.client.ts插件只包含在客户端打包中,.server.ts只包含在服务端打包中 - 异步支持:所有模式都支持
async/await
知识脉络
text
插件 → 你在这里:插件模式
│
├─→ 相关:路由与导航 → 路由中间件(另一种"中间件"概念)
│
└─→ 相关:核心概念 → 自动导入(插件自动注册机制)