---
name: vuejs-3-composition
description: "Build Vue 3 apps with Composition API, Pinia, Vue Router 4, and Vite. A Vue 3 expert who scaffolds components with the Composition API, wires up Pinia stores, configures Vue Router 4, and generates TypeScript-first code. Works with Nuxt 3 too."
license: MIT
---

# Vue 3 Composition API

A focused assistant for building Vue 3 applications the modern way: `<script setup>` single-file components, Pinia for state, Vue Router 4 for navigation, and Vite as the dev server and bundler. It writes TypeScript-first code with correctly typed props, emits, and store state, and it understands the Nuxt 3 conventions if your project lives there instead. This is for developers who have left the Options API behind and want idiomatic, reactive code without re-litigating `ref` vs `reactive` every time.

## When to use it

- Scaffolding a new component and you want `defineProps<T>()` / `defineEmits<T>()` with proper generic typing rather than runtime declarations.
- Standing up a Pinia store and wiring it into components without losing reactivity through destructuring.
- Configuring Vue Router 4 — nested routes, route guards, lazy-loaded views, typed `useRoute`/`useRouter`.
- Migrating an Options API component to `<script setup>`.
- Setting up or debugging a Vite + Vue project, including path aliases and the `@vitejs/plugin-vue` config.

## How it works

It generates `<script setup lang="ts">` SFCs using the core reactivity primitives — `ref`, `reactive`, `computed`, `watch`, `watchEffect` — and lifecycle hooks like `onMounted` and `onUnmounted`. Stores are written with Pinia's `defineStore` (setup-style, returning refs and functions), and consumed via `storeToRefs()` so reactivity survives destructuring. Router setup uses `createRouter` with `createWebHistory`, and it knows `defineAsyncComponent` and dynamic `import()` for code-splitting routes. For Nuxt 3 it follows file-based routing, auto-imports, and `useFetch`/`useAsyncData`.

## Example

```vue
<script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'

const store = useCounterStore()
const { count, double } = storeToRefs(store) // keeps reactivity
</script>

<template>
  <button @click="store.increment">{{ count }} / {{ double }}</button>
</template>
```

```ts
// stores/counter.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useCounterStore = defineStore('counter', () => {
  const count = ref(0)
  const double = computed(() => count.value * 2)
  function increment() { count.value++ }
  return { count, double, increment }
})
```

## Tips & gotchas

- Destructuring a store directly drops reactivity — always wrap state/getters in `storeToRefs()`, but keep **actions** off it (call them on the store object).
- `defineProps`/`defineEmits` are compiler macros: don't import them, and don't reference outer-scope variables in their type arguments.
- Props are reactive only if you don't destructure them (pre-3.5) — use `toRefs(props)` or access `props.x` directly.
- In Nuxt, `useFetch` dedupes by key; pass an explicit `key` when calling it twice with the same URL.

## FAQ

**`ref` or `reactive`?** Default to `ref` for everything — it's consistent, works for primitives, and survives reassignment. Reserve `reactive` for tightly grouped object state you'll never replace wholesale.

**Does it support the Options API?** It can read and migrate Options API code, but it generates Composition API / `<script setup>` by default.

**Pinia or Vuex?** Pinia. It's the official recommendation for Vue 3 and the skill targets its setup-store syntax.
