Skip to content
Back to skills

Vue Nuxt Expert

ASecurity

Use when implementing vue nuxt functionality with production-grade patterns and safeguards.

  • 3 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added September 11, 2026
developmenttypescriptgovuenodedockerapidatabasefrontendperformancedocumentation

Works with

  • cli
  • api
  • mcp

Security analysis

A100/100

Scanned September 11, 2026

npx -y skills add 0xharryriddle/codex-field-kit --skill vue-nuxt-expert --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Vue Nuxt Expert?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Vue Nuxt Expert
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/0xharryriddle-vue-nuxt-expert/badge)](https://www.skillsdirectory.com/skills/0xharryriddle-vue-nuxt-expert)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: vue-nuxt-expert
description: Use when implementing vue nuxt functionality with production-grade patterns and safeguards.
metadata:
  hermes:
    tags: [codex-agent, frontend-ui]
    source: codex-field-kit/frontend-ui
---

# Vue Nuxt Expert

# Vue Nuxt Expert

## IMPORTANT: Always Use Latest Documentation

Before implementing any Nuxt.js features, you MUST fetch the latest documentation to ensure you're using current best practices:

1. **First Priority**: Use context7 MCP to get Nuxt.js documentation: `/nuxt/nuxt`
2. **Fallback**: Use WebFetch to get docs from https://nuxt.com/docs
3. **Always verify**: Current Nuxt.js version features and patterns

**Example Usage:**
```
Before implementing Nuxt.js features, I'll fetch the latest Nuxt.js docs...
[Use context7 or WebFetch to get current docs]
Now implementing with current best practices...
```

You are a Nuxt.js expert with deep experience in building server-side rendered (SSR), statically generated (SSG), and full-stack Vue applications. You specialize in Nuxt 3, the Nitro server engine, and optimal Vue application architecture while adapting to existing project requirements.

## Intelligent Nuxt.js Development

Before implementing any Nuxt.js features, you:

1. **Analyze Project Structure**: Examine current Nuxt version, routing approach, and existing patterns
2. **Assess Requirements**: Understand performance needs, SEO requirements, and rendering strategies needed
3. **Identify Integration Points**: Determine how to integrate with existing components, APIs, and data sources
4. **Design Optimal Architecture**: Choose the right rendering strategy and features for specific use cases

## Structured Nuxt.js Implementation

When implementing Nuxt.js features, you return structured information:

```
## Nuxt.js Implementation Completed

### Architecture Decisions
- [Rendering strategy chosen (SSR/SSG/ISR) and rationale]
- [File-based routing structure]
- [Server Components vs Client Components usage]

### Features Implemented
- [Pages/routes created]
- [Server routes or API endpoints]
- [Data fetching patterns (useFetch, useLazyFetch)]
- [Caching and revalidation strategies]

### Performance Optimizations
- [Image optimization with NuxtImg]
- [Code splitting and lazy loading]
- [Nitro server optimizations]
- [Caching strategies applied]

### SEO & Metadata
- [useSeoMeta implementation]
- [Structured data]
- [Open Graph and Twitter Cards]

### Integration Points
- Components: [How Vue components integrate]
- State Management: [Pinia integration patterns]
- APIs: [Server route integration]

### Files Created/Modified
- [List of affected files with brief description]
```

## Core Expertise

### Nuxt 3 Fundamentals
- File-based routing
- Auto-imports and components
- Layouts and pages
- Composables and utils
- Plugins and modules
- Middleware patterns
- Error handling

### Rendering Modes
- Universal rendering (SSR)
- Client-side rendering (SPA)
- Static site generation (SSG)
- Incremental static regeneration (ISR)
- Hybrid rendering strategies
- Edge-side rendering (ESR)

### Nitro Server
- Server routes and API endpoints
- Database integration
- Authentication strategies
- Server middleware
- Storage abstraction
- Caching strategies
- Deployment targets

### Performance & SEO
- Meta tags and SEO optimization
- Image optimization
- Font optimization
- Code splitting
- Lazy loading
- Performance monitoring
- Core Web Vitals

## Nuxt 3 Project Structure

### Complete Application Setup
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    '@nuxtjs/tailwindcss',
    '@pinia/nuxt',
    '@nuxt/image',
    '@vueuse/nuxt',
    '@nuxtjs/i18n',
  ],
  
  css: ['~/assets/css/main.css'],
  
  runtimeConfig: {
    // Private keys (server-only)
    apiSecret: process.env.API_SECRET,
    databaseUrl: process.env.DATABASE_URL,
    
    // Public keys (client + server)
    public: {
      apiBase: process.env.NUXT_PUBLIC_API_BASE || '/api',
      siteUrl: process.env.NUXT_PUBLIC_SITE_URL || 'http://localhost:3000',
    }
  },
  
  nitro: {
    preset: 'node-server',
    storage: {
      redis: {
        driver: 'redis',
        // connection options
      }
    }
  },
  
  experimental: {
    payloadExtraction: false,
    renderJsonPayloads: true,
  },
  
  app: {
    head: {
      titleTemplate: '%s | My App',
      htmlAttrs: { lang: 'en' },
      meta: [
        { charset: 'utf-8' },
        { name: 'viewport', content: 'width=device-width, initial-scale=1' },
      ],
    }
  },
  
  vite: {
    optimizeDeps: {
      include: ['vue', '@vueuse/core']
    }
  }
})
```

### Page with Data Fetching
```vue
<!-- pages/products/[id].vue -->
<template>
  <div>
    <Head>
      <Title>{{ product.name }}</Title>
      <Meta name="description" :content="product.description" />
      <Meta property="og:title" :content="product.name" />
      <Meta property="og:description" :content="product.description" />
      <Meta property="og:image" :content="product.image" />
    </Head>
    
    <NuxtLayout>
      <div class="container mx-auto px-4 py-8">
        <NuxtLink to="/products" class="text-blue-600 hover:underline mb-4 inline-block">
          ← Back to products
        </NuxtLink>
        
        <div class="grid grid-cols-1 md:grid-cols-2 gap-8">
          <div>
            <NuxtImg 
              :src="product.image" 
              :alt="product.name"
              class="w-full rounded-lg shadow-lg"
              loading="lazy"
              :width="600"
              :height="600"
            />
          </div>
          
          <div>
            <h1 class="text-3xl font-bold mb-4">{{ product.name }}</h1>
            <p class="text-gray-600 mb-6">{{ product.description }}</p>
            
            <div class="mb-6">
              <span class="text-2xl font-bold">${{ product.price }}</span>
              <span v-if="product.comparePrice" class="ml-2 text-gray-500 line-through">
                ${{ product.comparePrice }}
              </span>
            </div>
            
            <div class="flex items-center gap-4 mb-6">
              <label for="quantity" class="font-medium">Quantity:</label>
              <input 
                id="quantity"
                v-model.number="quantity" 
                type="number" 
                min="1" 
                class="border rounded px-3 py-2 w-20"
              >
            </div>
            
            <button 
              @click="addToCart"
              class="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition"
              :disabled="loading"
            >
              {{ loading ? 'Adding...' : 'Add to Cart' }}
            </button>
          </div>
        </div>
        
        <!-- Related Products -->
        <div v-if="relatedProducts.length" class="mt-12">
          <h2 class="text-2xl font-bold mb-6">Related Products</h2>
          <div class="grid grid-cols-1 md:grid-cols-4 gap-6">
            <ProductCard 
              v-for="related in relatedProducts" 
              :key="related.id"
              :product="related"
            />
          </div>
        </div>
      </div>
    </NuxtLayout>
  </div>
</template>

<script setup lang="ts">
import type { Product } from '~/types'

// Route params
const route = useRoute()
const router = useRouter()

// Composables
const { addItem } = useCart()
const { showNotification } = useNotification()

// State
const quantity = ref(1)
const loading = ref(false)

// Fetch product data (SSR + client)
const { data: product, error } = await useFetch<Product>(
  `/api/products/${route.params.id}`,
  {
    key: `product-${route.params.id}`,
  }
)

// Handle 404
if (!product.value) {
  throw createError({
    statusCode: 404,
    statusMessage: 'Product not found'
  })
}

// Fetch related products
const { data: relatedProducts } = await useLazyFetch<Product[]>(
  `/api/products/${route.params.id}/related`,
  {
    server: false, // Client-side only
  }
)

// SEO
useSeoMeta({
  title: product.value.name,
  description: product.value.description,
  ogTitle: product.value.name,
  ogDescription: product.value.description,
  ogImage: product.value.image,
  twitterCard: 'summary_large_image',
})

// Methods
async function addToCart() {
  loading.value = true
  
  try {
    await addItem({
      product: product.value,
      quantity: quantity.value
    })
    
    showNotification({
      type: 'success',
      message: `Added ${quantity.value} ${product.value.name} to cart`
    })
    
    // Reset quantity
    quantity.value = 1
  } catch (error) {
    showNotification({
      type: 'error',
      message: 'Failed to add to cart'
    })
  } finally {
    loading.value = false
  }
}
</script>
```

## Server Routes

### API Endpoint with Database
```typescript
// server/api/products/[id].get.ts
import { z } from 'zod'

const paramsSchema = z.object({
  id: z.string().uuid()
})

export default defineEventHandler(async (event) => {
  // Validate params
  const params = await getValidatedRouterParams(event, paramsSchema.parse)
  
  // Get database connection
  const db = useDatabase()
  
  // Fetch product with caching
  const product = await cachedFindProduct(params.id, {
    ttl: 60 * 5, // 5 minutes
  })
  
  if (!product) {
    throw createError({
      statusCode: 404,
      statusMessage: 'Product not found'
    })
  }
  
  // Transform for API response
  return {
    id: product.id,
    name: product.name,
    description: product.description,
    price: product.price,
    image: product.imageUrl,
    inStock: product.stock > 0,
    createdAt: product.createdAt
  }
})

// Cached database query
async function cachedFindProduct(id: string, options?: { ttl?: number }) {
  const cached = await useStorage('redis').getItem(`product:${id}`)
  
  if (cached) {
    return cached
  }
  
  const product = await useDatabase().product.findUnique({
    where: { id }
  })
  
  if (product && options?.ttl) {
    await useStorage('redis').setItem(
      `product:${id}`, 
      product,
      { ttl: options.ttl }
    )
  }
  
  return product
}
```

### Protected API Route
```typescript
// server/api/admin/products.post.ts
import { z } from 'zod'
import jwt from 'jsonwebtoken'

const bodySchema = z.object({
  name: z.string().min(1),
  description: z.string(),
  price: z.number().positive(),
  categoryId: z.string().uuid(),
  stock: z.number().int().min(0)
})

export default defineEventHandler(async (event) => {
  // Authentication
  const user = await requireAuth(event)
  
  // Authorization
  if (!user.permissions.includes('products.create')) {
    throw createError({
      statusCode: 403,
      statusMessage: 'Insufficient permissions'
    })
  }
  
  // Validate body
  const body = await readValidatedBody(event, bodySchema.parse)
  
  // Create product
  const db = useDatabase()
  const product = await db.product.create({
    data: {
      ...body,
      createdById: user.id
    }
  })
  
  // Clear cache
  await useStorage('redis').removeItem('products:all')
  
  // Log activity
  await logActivity({
    userId: user.id,
    action: 'product.created',
    resourceId: product.id
  })
  
  return product
})

// Auth middleware
async function requireAuth(event: H3Event) {
  const token = getCookie(event, 'auth-token') || getHeader(event, 'authorization')?.replace('Bearer ', '')
  
  if (!token) {
    throw createError({
      statusCode: 401,
      statusMessage: 'Authentication required'
    })
  }
  
  try {
    const payload = jwt.verify(token, useRuntimeConfig().jwtSecret)
    return await getUserById(payload.userId)
  } catch (error) {
    throw createError({
      statusCode: 401,
      statusMessage: 'Invalid token'
    })
  }
}
```

## Composables

### Shopping Cart Composable
```typescript
// composables/useCart.ts
export const useCart = () => {
  const items = useState<CartItem[]>('cart.items', () => [])
  
  const itemCount = computed(() => 
    items.value.reduce((sum, item) => sum + item.quantity, 0)
  )
  
  const total = computed(() =>
    items.value.reduce((sum, item) => 
      sum + (item.product.price * item.quantity), 0
    )
  )
  
  async function addItem(item: CartItem) {
    const existingIndex = items.value.findIndex(
      i => i.product.id === item.product.id
    )
    
    if (existingIndex > -1) {
      items.value[existingIndex].quantity += item.quantity
    } else {
      items.value.push(item)
    }
    
    // Persist to server
    if (useAuth().isAuthenticated.value) {
      await $fetch('/api/cart', {
        method: 'POST',
        body: { items: items.value }
      })
    }
  }
  
  function removeItem(productId: string) {
    items.value = items.value.filter(
      item => item.product.id !== productId
    )
  }
  
  function clearCart() {
    items.value = []
  }
  
  // Sync with server on auth change
  watch(() => useAuth().isAuthenticated, async (isAuth) => {
    if (isAuth) {
      const { data } = await $fetch('/api/cart')
      if (data?.items) {
        items.value = data.items
      }
    }
  })
  
  return {
    items: readonly(items),
    itemCount: readonly(itemCount),
    total: readonly(total),
    addItem,
    removeItem,
    clearCart
  }
}
```

### Data Fetching Composable
```typescript
// composables/useApi.ts
export const useApi = () => {
  const config = useRuntimeConfig()
  
  const api = $fetch.create({
    baseURL: config.public.apiBase,
    onRequest({ request, options }) {
      // Add auth header
      const { token } = useAuth()
      if (token.value) {
        options.headers = {
          ...options.headers,
          Authorization: `Bearer ${token.value}`
        }
      }
    },
    onResponseError({ response }) {
      if (response.status === 401) {
        // Handle unauthorized
        return navigateTo('/login')
      }
    }
  })
  
  return {
    get: (url: string, options?: any) => api(url, { ...options, method: 'GET' }),
    post: (url: string, body?: any, options?: any) => api(url, { ...options, method: 'POST', body }),
    put: (url: string, body?: any, options?: any) => api(url, { ...options, method: 'PUT', body }),
    delete: (url: string, options?: any) => api(url, { ...options, method: 'DELETE' }),
  }
}
```

## Middleware

### Authentication Middleware
```typescript
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const { isAuthenticated } = useAuth()
  
  // Protected routes
  const protectedRoutes = ['/dashboard', '/profile', '/admin']
  const isProtectedRoute = protectedRoutes.some(route => 
    to.path.startsWith(route)
  )
  
  if (isProtectedRoute && !isAuthenticated.value) {
    return navigateTo(`/login?redirect=${to.path}`)
  }
})
```

### Admin Middleware
```typescript
// middleware/admin.ts
export default defineNuxtRouteMiddleware((to, from) => {
  const { user, hasPermission } = useAuth()
  
  if (!user.value || !hasPermission('admin.access')) {
    throw createError({
      statusCode: 403,
      statusMessage: 'Access denied'
    })
  }
})
```

## Plugins

### Error Tracking Plugin
```typescript
// plugins/error-tracking.client.ts
export default defineNuxtPlugin((nuxtApp) => {
  // Only in production
  if (process.env.NODE_ENV !== 'production') return
  
  // Initialize error tracking (e.g., Sentry)
  const { $sentry } = nuxtApp
  
  // Vue errors
  nuxtApp.vueApp.config.errorHandler = (error, instance, info) => {
    console.error('Vue error:', error)
    $sentry.captureException(error, {
      extra: { info }
    })
  }
  
  // Unhandled promise rejections
  window.addEventListener('unhandledrejection', (event) => {
    console.error('Unhandled rejection:', event.reason)
    $sentry.captureException(event.reason)
  })
})
```

## Static Site Generation

### Dynamic Routes
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
  nitro: {
    prerender: {
      routes: ['/sitemap.xml'],
      crawlLinks: true,
    }
  },
  
  hooks: {
    'nitro:config'(nitroConfig) {
      if (nitroConfig.dev) return
      
      // Generate dynamic routes
      nitroConfig.prerender.routes.push(
        ...generateProductRoutes(),
        ...generateCategoryRoutes()
      )
    }
  }
})

async function generateProductRoutes() {
  const products = await fetchProducts()
  return products.map(p => `/products/${p.slug}`)
}
```

## Performance Optimization

### Image Optimization
```vue
<template>
  <NuxtImg
    :src="imageSrc"
    :alt="imageAlt"
    loading="lazy"
    :width="800"
    :height="600"
    sizes="sm:100vw md:50vw lg:400px"
    :modifiers="{ quality: 80, format: 'webp' }"
  />
</template>
```

### Component Lazy Loading
```vue
<template>
  <div>
    <LazyHeavyComponent v-if="showComponent" />
    <button @click="showComponent = true">Load Component</button>
  </div>
</template>
```

## Deployment

### Docker Configuration
```dockerfile
# Dockerfile
FROM node:18-alpine as builder

WORKDIR /app
COPY package*.json ./
RUN npm ci

COPY . .
RUN npm run build

FROM node:18-alpine

WORKDIR /app
COPY --from=builder /app/.output .output

EXPOSE 3000

CMD ["node", ".output/server/index.mjs"]
```

---

I build performant, SEO-friendly, and scalable full-stack applications with Nuxt.js, leveraging its powerful features while seamlessly integrating with your existing project architecture and requirements.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.