404 Not Found

404 Not Found


nginx

Plugin System

Charlie needs to integrate the Stripe Payments SDK into MegaShop and also wants a global logging service. Stripe is loaded only on the client side, while logs are recorded on the server side. Traditionally, this would require manually importing and initializing the SDK in multiple places, but Nuxt 3's plugin system automates the entire process—register it once, and it's available globally.

1. What You'll Learn


2. A True Story of an Architect

(1) Pain Point: Third-party library initialization is scattered throughout the code

Charlie's MegaShop needs to integrate Stripe payments. Alice loaded Stripe.js on the shopping cart page, and Bob loaded it again on the checkout page—resulting in duplicate loads—and on some pages, it was forgotten, causing the payment button to be unavailable. The same goes for the logging tool; each component requires manually importing the logger.

(2) Solutions for the Nuxt Plugin System

Nuxt 3 plugins run automatically and are injected via provide for global use:

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

(3) Benefits: Register once for global access

Stripe and Logger are initialized only once within the plugin; all components are accessed via useNuxtApp().$stripe, eliminating the need for duplicate loading or manual imports.


3. Automatic Plugin Registration

(1) Plugin Registration and Execution Timing

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) Plugin Types and Naming Conventions

Name Runtime Environment Purpose
plugins/xxx.ts SSR + Client-side General-purpose plugin
plugins/xxx.server.ts Server-only Database/Keys
plugins/xxx.client.ts Client-only Browser SDK

(1) ▶ Example: General-Purpose Logging Plugin

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
    }
  }
})

Output:

TEXT
// Execution Successful

4. A Detailed Explanation of defineNuxtPlugin

(1) Provide/Inject Mechanism

Role API Description
Plugin Provided by provide: { xxx } Inject $xxx into NuxtApp
Component Usage useNuxtApp().$xxx Access to the Injected Service
Using Composable useNuxtApp().$xxx Accessing Composable

(1) ▶ Example: Stripe Payment Plugin (Client-Side)

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

  return {
    provide: {
      stripe
    }
  }
})

Output:

TEXT
// Execution Successful

(2) ▶ Example: Using a plugin in a component

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>

Output:

TEXT
// Execution Successful

(3) ▶ Example: Server-Side Database Plugin

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
    }
  }
})

Output:

TEXT
// Execution Successful

5. Distinguishing Between Server-Side and Client-Side Plugins

(1) ▶ Example: Notification Plugin (Client-side, using browser APIs)

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 } } }
})

Output:

TEXT
// Execution Successful

(2) ▶ Example: Environment Detection Plugin (General-Purpose)

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 }
    }
  }
})

Output:

TEXT
// Execution Successful

(1) Comparison of Server-Side vs. Client-Side Plugins

Dimension .server.ts .client.ts .ts (General)
SSR Execution
Client Execution
Browser API ❌ Not available ✅ Available ⚠️ Check first
Node.js API ✅ Available ❌ Unavailable ⚠️ To Be Determined
Private Data ✅ Secure ❌ May Be Exposed ⚠️ Caution

6. Plugin Execution Order

(1) Sorting Rules

Rule Example Execution Order
Sorted alphabetically by filename 01-aaa.ts → 02-bbb.ts 01 is executed first
Numeric Prefix Control 01-env.ts → 02-db.ts → 03-api.ts In numerical order
server before client db.server.ts → stripe.client.ts server first during SSR
nuxt.config plugins at the end plugins injected via modules framework plugins are executed first

(1) ▶ Example: Controlling the Execution Order of Plugins

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

Output:

TEXT
Execution Successful

(2) ▶ Example: Using Other Plugins Within a Plugin

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 } }
})

Output:

TEXT
// Execution Successful

7. Comprehensive Example: The MegaShop Plugin System

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 } }
})

❓ FAQ

Q What is the difference between a plugin and a Composable?
A A plugin runs automatically once when the app starts and is used to initialize third-party libraries. A Composable is called on demand within a component and is used for reusing logic. Stripe uses a plugin for initialization and a Composable for price formatting.
Q Why do the names of injected services have a $ prefix?
A Nuxt recommends prefixing injected services with a $ ($stripe/$logger) to avoid conflicts with variables within components. This is a Nuxt naming convention, not a requirement.
Q Can I use the Pinia Store in a plugin?
A Yes, but you need to be mindful of the timing. Pinia is initialized before the plugin, so you can use useXxxStore() within the plugin. However, it's recommended to manage the Store within a Composable and use the plugin solely for initialization.
Q When are client-side plugins executed?
A They are executed during client-side hydration. .client.ts plugins are not run during the SSR phase. When a page is loaded for the first time, client-side plugins are executed after hydration is complete.
Q Can I dynamically register plugins?
A Not recommended. Nuxt plugins are resolved at build time and automatically executed at runtime. If you need to delay initialization, use lazy loading within the plugin (such as dynamic imports).
Q What should I do if a third-party SDK (such as Stripe) loads slowly?
A Dynamically load the SDK in the .client.ts plugin to avoid blocking hydration: const Stripe = (await import('@stripe/stripe-js')).default. Or use the <script> tag to preload it in the <head> section.

📖 Summary


📝 Exercises

  1. Basic Exercise (Difficulty: ⭐): Create a logger plugin and use useNuxtApp().$logger.info() to log messages within a component.
  2. Advanced Exercise (Difficulty: ⭐⭐): Create a Stripe client plugin and a database server plugin, and verify that they run in different environments
  3. Challenge (Difficulty: ⭐⭐⭐): Create an analytics plugin that uses nuxtApp.hook('page:finish') to automatically track page views and works with the logger plugin to record data.

---|

Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

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

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