Claude
Skills
Sign in
Back

vue-nuxt-expert

Included with Lifetime
$97 forever

# Vue 3 & Nuxt 3 Expert

Web Dev

What this skill does

# Vue 3 & Nuxt 3 Expert

## Section 1: Overview

**Risk Level**: MEDIUM

**Expertise Areas**:
- Vue 3.4+ with Composition API and TypeScript
- Nuxt 3.10+ server-side rendering (SSR) and static site generation (SSG)
- State management with Pinia and composables
- Performance optimization and Core Web Vitals
- Client-side security (XSS, CSRF, injection attacks)
- Modern build tooling (Vite, Nitro)

**Target Users**: Frontend engineers building modern, performant, type-safe web applications

**Key Focus**: Type-safe component architecture, composable logic, SSR/SSG patterns, and client-side security

---

## Section 2: Core Principles

1. **TDD First** - Write tests before implementation using Vitest and Vue Test Utils
2. **Performance Aware** - Optimize reactivity, use computed over methods, implement lazy loading
3. **Type Safety** - Use TypeScript strict mode with proper component and composable typing
4. **Composable-First** - Extract reusable logic into composables for maximum reusability
5. **Security-Conscious** - Prevent XSS, validate inputs, configure CSP headers
6. **SSR-Compatible** - Always consider server-side rendering implications

---

## Section 3: Implementation Workflow (TDD)

### Step 1: Write Failing Test First

```typescript
// tests/components/UserCard.test.ts
import { describe, it, expect, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createTestingPinia } from '@pinia/testing'
import UserCard from '~/components/UserCard.vue'

describe('UserCard', () => {
  it('displays user name and email', () => {
    const wrapper = mount(UserCard, {
      props: {
        user: {
          id: '1',
          name: 'John Doe',
          email: '[email protected]'
        }
      },
      global: {
        plugins: [createTestingPinia()]
      }
    })

    expect(wrapper.text()).toContain('John Doe')
    expect(wrapper.text()).toContain('[email protected]')
  })

  it('emits select event when clicked', async () => {
    const wrapper = mount(UserCard, {
      props: {
        user: { id: '1', name: 'John', email: '[email protected]' }
      }
    })

    await wrapper.trigger('click')
    expect(wrapper.emitted('select')).toBeTruthy()
    expect(wrapper.emitted('select')[0]).toEqual(['1'])
  })

  it('shows loading state', () => {
    const wrapper = mount(UserCard, {
      props: {
        user: null,
        loading: true
      }
    })

    expect(wrapper.find('[data-testid="loading-skeleton"]').exists()).toBe(true)
  })
})
```

### Step 2: Write Composable Tests

```typescript
// tests/composables/useAsyncData.test.ts
import { describe, it, expect, vi } from 'vitest'
import { useAsyncData } from '~/composables/useAsyncData'

describe('useAsyncData', () => {
  it('fetches data successfully', async () => {
    const mockData = { id: 1, name: 'Test' }
    const fetcher = vi.fn().mockResolvedValue(mockData)

    const { data, loading, error, execute } = useAsyncData(fetcher, {
      immediate: false
    })

    expect(data.value).toBeNull()
    expect(loading.value).toBe(false)

    await execute()

    expect(fetcher).toHaveBeenCalledOnce()
    expect(data.value).toEqual(mockData)
    expect(error.value).toBeNull()
  })

  it('handles errors', async () => {
    const mockError = new Error('Network error')
    const fetcher = vi.fn().mockRejectedValue(mockError)
    const onError = vi.fn()

    const { data, error, execute } = useAsyncData(fetcher, {
      immediate: false,
      onError
    })

    await execute()

    expect(error.value).toBe(mockError)
    expect(data.value).toBeNull()
    expect(onError).toHaveBeenCalledWith(mockError)
  })

  it('transforms data', async () => {
    const fetcher = vi.fn().mockResolvedValue({ users: [{ id: 1 }] })
    const transform = (data: any) => data.users

    const { data, execute } = useAsyncData(fetcher, {
      immediate: false,
      transform
    })

    await execute()

    expect(data.value).toEqual([{ id: 1 }])
  })
})
```

### Step 3: Write Pinia Store Tests

```typescript
// tests/stores/user.test.ts
import { describe, it, expect, beforeEach, vi } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useUserStore } from '~/stores/user'

// Mock $fetch
vi.stubGlobal('$fetch', vi.fn())

describe('useUserStore', () => {
  beforeEach(() => {
    setActivePinia(createPinia())
    vi.clearAllMocks()
  })

  it('logs in user successfully', async () => {
    const mockResponse = {
      user: { id: '1', email: '[email protected]', name: 'Test', roles: [] },
      token: 'mock-token'
    }
    vi.mocked($fetch).mockResolvedValue(mockResponse)

    const store = useUserStore()
    await store.login('[email protected]', 'password')

    expect($fetch).toHaveBeenCalledWith('/api/auth/login', {
      method: 'POST',
      body: { email: '[email protected]', password: 'password' }
    })
    expect(store.currentUser).toEqual(mockResponse.user)
    expect(store.isAuthenticated).toBe(true)
  })

  it('checks user roles correctly', async () => {
    const store = useUserStore()
    store.currentUser = {
      id: '1',
      email: '[email protected]',
      name: 'Admin',
      roles: ['admin', 'user']
    }

    expect(store.hasRole('admin')).toBe(true)
    expect(store.hasRole('superadmin')).toBe(false)
  })

  it('clears state on logout', async () => {
    vi.mocked($fetch).mockResolvedValue({})

    const store = useUserStore()
    store.currentUser = { id: '1', email: '[email protected]', name: 'Test', roles: [] }
    store.token = 'token'

    await store.logout()

    expect(store.currentUser).toBeNull()
    expect(store.token).toBeNull()
    expect(store.isAuthenticated).toBe(false)
  })
})
```

### Step 4: Implement Minimum Code to Pass

```vue
<!-- components/UserCard.vue -->
<script setup lang="ts">
interface User {
  id: string
  name: string
  email: string
}

const props = defineProps<{
  user: User | null
  loading?: boolean
}>()

const emit = defineEmits<{
  select: [id: string]
}>()

const handleClick = () => {
  if (props.user) {
    emit('select', props.user.id)
  }
}
</script>

<template>
  <div @click="handleClick" class="user-card">
    <div v-if="loading" data-testid="loading-skeleton" class="skeleton">
      Loading...
    </div>
    <template v-else-if="user">
      <h3>{{ user.name }}</h3>
      <p>{{ user.email }}</p>
    </template>
  </div>
</template>
```

### Step 5: Run Full Verification

```bash
# Run all tests
npm run test

# Run tests with coverage
npm run test:coverage

# Run specific test file
npm run test tests/components/UserCard.test.ts

# Type checking
npm run typecheck

# Lint
npm run lint

# Build to ensure no errors
npm run build
```

---

## Section 4: Performance Patterns

### Pattern 1: Use Computed Over Methods

**Bad - Method called on every render:**
```vue
<script setup lang="ts">
const items = ref([...])

// ❌ BAD: Recalculates on every render
const getFilteredItems = () => {
  return items.value.filter(item => item.active)
}
</script>

<template>
  <div v-for="item in getFilteredItems()" :key="item.id">
    {{ item.name }}
  </div>
</template>
```

**Good - Computed caches result:**
```vue
<script setup lang="ts">
const items = ref([...])

// ✅ GOOD: Only recalculates when items change
const filteredItems = computed(() => {
  return items.value.filter(item => item.active)
})
</script>

<template>
  <div v-for="item in filteredItems" :key="item.id">
    {{ item.name }}
  </div>
</template>
```

### Pattern 2: Use shallowRef for Large Objects

**Bad - Deep reactivity on large objects:**
```typescript
// ❌ BAD: Creates deep reactive proxy for entire object
const largeDataset = ref<DataItem[]>([])

// Every nested property becomes reactive
largeDataset.value = await fetchLargeDataset()
```

**Good - Shallow reactivity when deep tracking not needed:**
```typescript
// ✅ GOOD: Only tracks the reference, not nested properties
const largeDataset = shallowRef<DataItem[]>([])

// Manually trigger updates
largeDataset.value = await fetchLargeData

Related in Web Dev