---
name: vue-nuxt
description: Vue 3 and Nuxt 3 for JARVIS AI Assistant UI development with security-first patterns
model: sonnet
risk_level: MEDIUM
version: 1.0.0
---
# Vue 3 / Nuxt 3 Development Skill
> **File Organization**: This skill uses split structure. See `references/` for advanced patterns and security examples.
## 1. Overview
This skill provides expertise for building the JARVIS AI Assistant user interface using Vue 3 and Nuxt 3. It focuses on creating responsive, performant 3D HUD interfaces with security-first development practices.
**Risk Level**: MEDIUM - Handles user input, renders dynamic content, potential XSS vectors
**Primary Use Cases**:
- Building reactive 3D HUD components for JARVIS interface
- Server-side rendering for initial load performance
- Client-side state management integration
- Secure handling of user inputs and API responses
## 2. Core Responsibilities
### 2.1 Fundamental Principles
1. **TDD First**: Write tests before implementation - red/green/refactor cycle
2. **Performance Aware**: Use computed, shallowRef, lazy components for optimal reactivity
3. **Composition API First**: Use Vue 3 Composition API with `
```
### 4.2 Input Sanitization Pattern
```typescript
// composables/useSanitize.ts
import DOMPurify from 'isomorphic-dompurify'
export function useSanitize() {
const sanitizeHTML = (dirty: string): string => {
// ✅ Strict sanitization for any HTML content
return DOMPurify.sanitize(dirty, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'span'],
ALLOWED_ATTR: ['class']
})
}
const sanitizeText = (input: string): string => {
// ✅ Strip all HTML for plain text
return DOMPurify.sanitize(input, { ALLOWED_TAGS: [] })
}
return { sanitizeHTML, sanitizeText }
}
```
### 4.3 Secure API Route Pattern
```typescript
// server/api/jarvis/command.post.ts
import { z } from 'zod'
// ✅ Define strict schema for command validation
const commandSchema = z.object({
action: z.enum(['status', 'control', 'query']),
target: z.string().max(100).regex(/^[a-zA-Z0-9-_]+$/),
parameters: z.record(z.string()).optional()
})
export default defineEventHandler(async (event) => {
const body = await readBody(event)
// ✅ Validate input against schema
const result = commandSchema.safeParse(body)
if (!result.success) {
throw createError({
statusCode: 400,
message: 'Invalid command format' // ✅ Generic error message
})
}
// ✅ Process validated command
const command = result.data
// Never log sensitive data
console.log(`Processing command: ${command.action}`)
return { success: true, commandId: generateSecureId() }
})
```
### 4.4 Secure Environment Configuration
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
// ✅ Security headers
routeRules: {
'/**': {
headers: {
'Content-Security-Policy': "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'",
'X-Content-Type-Options': 'nosniff',
'X-Frame-Options': 'DENY',
'X-XSS-Protection': '1; mode=block'
}
}
},
// ✅ Runtime config - secrets stay server-side
runtimeConfig: {
apiSecret: process.env.API_SECRET, // Server only
public: {
apiBase: '/api' // Client accessible
}
},
// ✅ Disable devtools in production
devtools: { enabled: process.env.NODE_ENV === 'development' }
})
```
### 4.5 3D HUD Component Integration
```vue
```
## 5. Implementation Workflow (TDD)
### 5.1 Step 1: Write Failing Test First
Always start by writing tests that define expected behavior:
```typescript
// tests/components/VoiceIndicator.test.ts
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import VoiceIndicator from '@/components/VoiceIndicator.vue'
describe('VoiceIndicator', () => {
it('displays idle state by default', () => {
const wrapper = mount(VoiceIndicator)
expect(wrapper.find('.indicator').classes()).toContain('idle')
expect(wrapper.text()).toContain('Ready')
})
it('shows listening state when active', async () => {
const wrapper = mount(VoiceIndicator, {
props: { isListening: true }
})
expect(wrapper.find('.indicator').classes()).toContain('listening')
expect(wrapper.find('.pulse-animation').exists()).toBe(true)
})
it('emits cancel event on escape key', async () => {
const wrapper = mount(VoiceIndicator, {
props: { isListening: true }
})
await wrapper.trigger('keydown.escape')
expect(wrapper.emitted('cancel')).toBeTruthy()
})
})
```
### 5.2 Step 2: Implement Minimum to Pass
Write only enough code to make the tests pass:
```vue
Ready
```
### 5.3 Step 3: Refactor if Needed
After tests pass, improve code quality without changing behavior. Re-run tests after each refactor.
### 5.4 Step 4: Run Full Verification
```bash
# Run all verification steps before committing
npx vitest run # Unit tests
npx eslint . --ext .vue,.ts # Linting
npx nuxi typecheck # Type checking
npm run build # Build verification
```
## 6. Performance Patterns
### 6.1 Computed Properties for Derived State
```typescript
// ❌ BAD - Recalculates in template on every render
{{ items.filter(i => i.active).length }} active
// ✅ GOOD - Cached until dependencies change
const activeCount = computed(() => items.value.filter(i => i.active).length)
{{ activeCount }} active
```
### 6.2 shallowRef for Large Objects
```typescript
// ❌ BAD - Deep reactivity on large 3D data
const meshData = ref({ vertices: new Float32Array(100000), ... })
// ✅ GOOD - Shallow reactivity, manual trigger
const meshData = shallowRef({ vertices: new Float32Array(100000), ... })
// Trigger update explicitly
meshData.value = { ...newData }
triggerRef(meshData)
```
### 6.3 defineAsyncComponent for Lazy Loading
```typescript
// ❌ BAD - All components loaded upfront
import HeavyChart from '@/components/HeavyChart.vue'
// ✅ GOOD - Load only when needed
const HeavyChart = defineAsyncComponent(() =>
import('@/components/HeavyChart.vue')
)
// With loading state
const HeavyChart = defineAsyncComponent({
loader: () => import('@/components/HeavyChart.vue'),
loadingComponent: LoadingSpinner,
delay: 200
})
```
### 6.4 v-memo for List Optimization
```vue
```
### 6.5 Virtual Scrolling for Long Lists
```vue
{{ data.name }}
```
### 6.6 Debounced Watchers
```typescript
// ❌ BAD - Fires on every keystroke
watch(searchQuery, async (query) => {
results.value = await searchAPI(query)
})
// ✅ GOOD - Debounced to reduce API calls
import { watchDebounced } from '@vueuse/core'
watchDebounced(
searchQuery,
async (query) => {
results.value = await searchAPI(query)
},
{ debounce: 300 }
)
// Alternative with manual debounce
watch(searchQuery, useDebounceFn(async (query) => {
results.value = await searchAPI(query)
}, 300))
```
## 7. Security Standards
### 7.1 Known Vulnerabilities (CVE Research)
| CVE | Severity | Description | Mitigation |
|-----|----------|-------------|------------|
| CVE-2024-34344 | HIGH | Nuxt RCE via test component | Update to Nuxt 3.12.4+ |
| CVE-2024-23657 | HIGH | Devtools path traversal/RCE | Update devtools to 1.3.9+ |
| CVE-2023-3224 | CRITICAL | Dev server code injection | Update to Nuxt 3.4.4+, never expose dev server |
**See**: `references/security-examples.md` for detailed mitigation code
### 5.2 OWASP Top 10 Coverage
| OWASP Category | Risk | Mitigation Strategy |
|----------------|------|---------------------|
| A01 Broken Access Control | HIGH | Server-side route guards, middleware auth |
| A03 Injection | HIGH | Input validation with Zod, parameterized queries |
| A05 Security Misconfiguration | MEDIUM | CSP headers, secure nuxt.config |
| A07 XSS | HIGH | v-text directive, DOMPurify sanitization |
### 5.3 Input Validation Framework
```typescript
// ❌ DANGEROUS - Direct v-html with user input
// ✅ SECURE - Sanitized HTML or plain text
```
### 5.4 Authentication Middleware
```typescript
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to) => {
const { authenticated } = useAuthState()
if (!authenticated.value && to.meta.requiresAuth) {
return navigateTo('/login')
}
})
```
## 6. Testing & Quality
### 6.1 Security Testing
```typescript
// tests/security/xss.test.ts
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import HUDPanel from '@/components/HUDPanel.vue'
describe('XSS Prevention', () => {
it('should sanitize malicious input', () => {
const wrapper = mount(HUDPanel, {
props: {
title: 'Hello'
}
})
expect(wrapper.html()).not.toContain('