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
- When the "plugins/" directory is automatically registered and executed
- defineNuxtPlugin: Using
provideandinject - Server-side/client-side plugins: Distinguished by the .server.ts and .client.ts file extensions
- Control of Plugin Execution Order
- Hands-On Guide to the MegaShop Payment SDK and Logging Plugin
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:
// 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
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
// 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:
// 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)
// plugins/stripe.client.ts
export default defineNuxtPlugin(() => {
const config = useRuntimeConfig()
const stripe = window.Stripe(config.public.stripePublishableKey)
return {
provide: {
stripe
}
}
})
Output:
// Execution Successful
(2) ▶ Example: Using a plugin in a component
<!-- 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:
// Execution Successful
(3) ▶ Example: Server-Side Database Plugin
// 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:
// Execution Successful
5. Distinguishing Between Server-Side and Client-Side Plugins
(1) ▶ Example: Notification Plugin (Client-side, using browser APIs)
// 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:
// Execution Successful
(2) ▶ Example: Environment Detection Plugin (General-Purpose)
// 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:
// 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
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:
Execution Successful
(2) ▶ Example: Using Other Plugins Within a Plugin
// 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:
// Execution Successful
7. Comprehensive Example: The MegaShop Plugin System
// 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
}
}
}
})
// 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 } }
})
// plugins/03-stripe.client.ts - Payment SDK
export default defineNuxtPlugin(() => {
const config = useRuntimeConfig()
const stripe = window.Stripe(config.public.stripePublishableKey)
return { provide: { stripe } }
})
// 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
useXxxStore() within the plugin. However, it's recommended to manage the Store within a Composable and use the plugin solely for initialization.const Stripe = (await import('@stripe/stripe-js')).default. Or use the <script> tag to preload it in the <head> section.📖 Summary
- The
plugins/directory is automatically registered, and files are executed in alphabetical order by filename defineNuxtPlugin+provideinject global services; components access them viauseNuxtApp().$xxx- The .server.ts and .client.ts file extensions distinguish between execution environments to avoid conflicts between browser and Node APIs
- Numeric prefixes control the execution order and ensure that dependencies are handled correctly
- MegaShop integrates third-party services such as Stripe payments, Logger, and Notifications via plugins
📝 Exercises
- Basic Exercise (Difficulty: ⭐): Create a logger plugin and use
useNuxtApp().$logger.info()to log messages within a component. - Advanced Exercise (Difficulty: ⭐⭐): Create a Stripe client plugin and a database server plugin, and verify that they run in different environments
- 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.
---|



