Skip to content

插件模式

通过文件名后缀控制插件在服务端还是客户端执行。这是 Nuxt 的约定式配置——文件名即配置

三种模式

文件名后缀SSR客户端典型用途
.ts(无后缀)通用逻辑
.client.ts浏览器 API、统计代码
.server.ts服务端初始化

为什么需要区分执行环境?

  • 客户端插件使用了 windowdocumentlocalStorage 等,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)
  • 使用 canvasWebGL 的库

.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 并设置用户信息
      // 注意:不能在这里发异步请求
    }
  })
})

注意事项

  1. 文件名严格:必须是 .client.ts.server.ts,不支持 .browser.ts 等其他命名
  2. 避免状态不一致:两端都执行的插件要确保状态在 SSR 和客户端之间一致
  3. ClientOnly 替代:简单的客户端逻辑可以用 <ClientOnly> 而非客户端插件
  4. 打包体积.client.ts 插件只包含在客户端打包中,.server.ts 只包含在服务端打包中
  5. 异步支持:所有模式都支持 async/await

知识脉络

text
插件 → 你在这里:插件模式

        ├─→ 相关:路由与导航 → 路由中间件(另一种"中间件"概念)

        └─→ 相关:核心概念 → 自动导入(插件自动注册机制)

基于 Nuxt 4 官方文档整理编写