Nuxt: 插件系统

最后更新:2026-08-26

Charlie 需要集成 Stripe 支付 SDK 到 MegaShop,还想要全局日志服务。Stripe 只在客户端加载,日志在服务端记录。传统方式要在多个地方手动 import 初始化,Nuxt 3 插件系统让一切自动化——注册一次,全局可用。

1. 你将学到


2. 一个架构师的真实故事

(1) 痛点:第三方库初始化散落各处

Charlie 的 MegaShop 需要集成 Stripe 支付。Alice 在购物车页面加载了 Stripe.js,Bob 在结算页又加载了一次——重复加载,而且有些页面忘了加载导致支付按钮不可用。日志工具也是,每个组件都要手动 import logger。

(2) Nuxt 插件系统的解法

Nuxt 3 插件自动执行,provide 注入全局可用:

TYPESCRIPT
// plugins/stripe.client.ts
export default defineNuxtPlugin(() => {
  const stripe = Stripe(config.public.stripePublishableKey)
  return { provide: { stripe } }
})

(3) 收益:一次注册全局可用

Stripe 和 Logger 只在插件中初始化一次,所有组件通过 useNuxtApp().$stripe 访问,不再重复加载和手动 import。


3. 插件自动注册

(1) 插件注册与执行时序

100%
sequenceDiagram
    participant N as Nuxt App
    participant P1 as 01-env.server.ts
    participant P2 as 02-stripe.client.ts
    participant P3 as 03-logger.ts
    participant C as Components

    N->>P1: Execute server plugins first
    N->>P2: Execute client plugins on hydration
    N->>P3: Execute universal plugins
    N->>C: Components can use $stripe, $logger

(2) 插件类型与命名

命名 执行环境 用途
plugins/xxx.ts SSR + 客户端 通用插件
plugins/xxx.server.ts 仅服务端 数据库/密钥
plugins/xxx.client.ts 仅客户端 浏览器 SDK

▶ 示例:通用日志插件

TYPESCRIPT
// plugins/logger.ts
export default defineNuxtPlugin(() => {
  const logger = {
    info: (message: string, data?: any) => {
      console.log(`[INFO] ${message}`, data || '')
    },
    warn: (message: string, data?: any) => {
      console.warn(`[WARN] ${message}`, data || '')
    },
    error: (message: string, data?: any) => {
      console.error(`[ERROR] ${message}`, data || '')
    }
  }

  return {
    provide: {
      logger
    }
  }
})

输出:

TEXT 📖 仅展示
// 执行成功

4. defineNuxtPlugin 详解

(1) provide / inject 机制

角色 API 说明
插件提供 provide: { xxx } 注入 $xxx 到 NuxtApp
组件使用 useNuxtApp().$xxx 访问注入的服务
Composable 使用 useNuxtApp().$xxx Composable 中访问

▶ 示例:Stripe 支付插件(客户端)

TYPESCRIPT
// plugins/stripe.client.ts
export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig()
  const stripe = window.Stripe(config.public.stripePublishableKey)

  return {
    provide: {
      stripe
    }
  }
})

输出:

TEXT 📖 仅展示
// 执行成功

▶ 示例:在组件中使用插件

VUE
<!-- pages/checkout.vue -->
<template>
  <div>
    <h1>Checkout - ${{ total }} USD</h1>
    <div ref="cardElement"></div>
    <button @click="processPayment" :disabled="processing">
      Pay ${{ total }} USD
    </button>
  </div>
</template>

<script setup lang="ts">
const { $stripe } = useNuxtApp()
const { $logger } = useNuxtApp()

const cart = useState<any[]>('cart')
const total = computed(() => cart.value.reduce((s, i) => s + i.price * i.quantity, 0))
const processing = ref(false)

async function processPayment() {
  processing.value = true
  try {
    $logger.info('Processing payment', { amount: total.value })
    // Use Stripe for payment
    const result = await $stripe.confirmCardPayment('{PAYMENT_INTENT_SECRET}')
    if (result.error) {
      $logger.error('Payment failed', result.error)
    } else {
      $logger.info('Payment succeeded')
      navigateTo('/order/success')
    }
  } finally {
    processing.value = false
  }
}
</script>

输出:

TEXT 📖 仅展示
// 执行成功

▶ 示例:服务端数据库插件

TYPESCRIPT
// plugins/database.server.ts
export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig()

  // Only runs on server - database connection with private key
  const db = createDatabaseConnection(config.databaseUrl)

  return {
    provide: {
      db
    }
  }
})

输出:

TEXT 📖 仅展示
// 执行成功

5. 服务端/客户端插件区分

▶ 示例:通知插件(客户端,用浏览器 API)

TYPESCRIPT
// plugins/notification.client.ts
export default defineNuxtPlugin(() => {
  function requestPermission() {
    if ('Notification' in window) {
      Notification.requestPermission()
    }
  }

  function send(title: string, body: string) {
    if (Notification.permission === 'granted') {
      new window.Notification(title, { body, icon: '/favicon.ico' })
    }
  }

  // Auto-request permission on first visit
  requestPermission()

  return { provide: { notification: { send, requestPermission } } }
})

输出:

TEXT 📖 仅展示
// 执行成功

▶ 示例:环境检测插件(通用)

TYPESCRIPT
// plugins/env.ts
export default defineNuxtPlugin(() => {
  const isServer = import.meta.server
  const isClient = import.meta.client
  const isDev = import.meta.dev

  return {
    provide: {
      env: { isServer, isClient, isDev }
    }
  }
})

输出:

TEXT 📖 仅展示
// 执行成功

(1) 服务端 vs 客户端插件对比

维度 .server.ts .client.ts .ts (通用)
SSR 执行
客户端执行
浏览器 API ❌ 不可用 ✅ 可用 ⚠️ 需判断
Node.js API ✅ 可用 ❌ 不可用 ⚠️ 需判断
私密数据 ✅ 安全 ❌ 会暴露 ⚠️ 注意

6. 插件执行顺序

(1) 排序规则

规则 示例 执行顺序
按文件名字母排序 01-aaa.ts → 02-bbb.ts 01 先执行
数字前缀控制 01-env.ts → 02-db.ts → 03-api.ts 按数字顺序
server 先于 client db.server.ts → stripe.client.ts SSR 时 server 先
nuxt.config 插件最后 modules 注入的插件 框架插件先执行

▶ 示例:控制插件执行顺序

TEXT 📖 仅展示
plugins/
├── 01-runtime-env.ts       # First: setup environment
├── 02-database.server.ts   # Second: connect database
├── 03-logger.ts            # Third: init logger
├── 04-stripe.client.ts     # Fourth: init Stripe
└── 05-analytics.client.ts  # Fifth: init analytics

输出:

TEXT 📖 仅展示
执行成功

▶ 示例:在插件中使用其他插件

TYPESCRIPT
// plugins/05-analytics.client.ts
export default defineNuxtPlugin((nuxtApp) => {
  // Access previously registered plugin
  const { $logger } = nuxtApp

  $logger.info('Analytics plugin initialized')

  const analytics = {
    track(event: string, data?: any) {
      $logger.info(`Track: ${event}`, data)
      // Send to analytics service
    },
    pageView(path: string) {
      $logger.info(`Page view: ${path}`)
    }
  }

  // Auto-track page views
  nuxtApp.hook('page:finish', () => {
    analytics.pageView(window.location.pathname)
  })

  return { provide: { analytics } }
})

输出:

TEXT 📖 仅展示
// 执行成功

7. 综合示例:MegaShop 插件体系

TYPESCRIPT
// plugins/01-config.ts - Runtime config helper
export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig()
  return {
    provide: {
      config: {
        apiBase: config.public.apiBase,
        currency: 'USD',
        locale: 'en-US',
        maxCartItems: 99
      }
    }
  }
})
TYPESCRIPT
// plugins/02-logger.ts - Structured logging
export default defineNuxtPlugin(() => {
  const logger = {
    info(msg: string, ctx?: Record<string, any>) {
      console.log(JSON.stringify({ level: 'info', msg, ctx, ts: Date.now() }))
    },
    error(msg: string, ctx?: Record<string, any>) {
      console.error(JSON.stringify({ level: 'error', msg, ctx, ts: Date.now() }))
    }
  }
  return { provide: { logger } }
})
TYPESCRIPT
// plugins/03-stripe.client.ts - Payment SDK
export default defineNuxtPlugin(() => {
  const config = useRuntimeConfig()
  const stripe = window.Stripe(config.public.stripePublishableKey)
  return { provide: { stripe } }
})
TYPESCRIPT
// plugins/04-notification.client.ts - Browser notifications
export default defineNuxtPlugin(() => {
  const notify = {
    send(title: string, body: string) {
      if ('Notification' in window && Notification.permission === 'granted') {
        new Notification(title, { body })
      }
    }
  }
  return { provide: { notify } }
})

❓ 常见问题

Q 插件和 Composable 有什么区别?
A 插件在应用启动时自动执行一次,用于初始化第三方库。Composable 在组件中按需调用,用于逻辑复用。Stripe 初始化用插件,价格格式化用 Composable。
Q provide 注入的名字为什么有 $ 前缀?
A Nuxt 约定注入的服务加 $ 前缀($stripe/$logger),避免与组件内变量冲突。这是 Nuxt 的命名约定,不是强制要求。
Q 插件里能使用 Pinia Store 吗?
A 可以,但需要注意时机。Pinia 在插件之前初始化,所以在插件中可以使用 useXxxStore()。但建议在 Composable 中操作 Store,插件只做初始化。
Q 客户端插件什么时候执行?
A 在客户端水合时执行。SSR 阶段不会运行 .client.ts 插件。页面首次加载时,客户端插件在水合完成后执行。
Q 能不能动态注册插件?
A 不推荐。Nuxt 插件在构建时确定,运行时自动执行。如果需要延迟初始化,在插件内部用懒加载模式(如动态 import)。
Q 第三方 SDK(如 Stripe)加载慢怎么办?
A 在 .client.ts 插件中动态加载 SDK,不阻塞水合:const Stripe = (await import('@stripe/stripe-js')).default。或用 <script> 标签在 head 中预加载。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建一个 logger 插件,在组件中用 useNuxtApp().$logger.info() 记录日志
  2. 进阶题(难度⭐⭐):创建 Stripe 客户端插件 + 数据库服务端插件,验证两者在不同环境执行
  3. 挑战题(难度⭐⭐⭐):创建 analytics 插件,利用 nuxtApp.hook('page:finish') 自动追踪页面浏览,并与 logger 插件协作记录

---|

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏