ワンクリックで
vue-frontend
Vue 3 Composition API + TypeScript patterns. Load when building components, composables, Pinia state, or reviewing Vue
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Vue 3 Composition API + TypeScript patterns. Load when building components, composables, Pinia state, or reviewing Vue
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
External API evaluation and MCP server wrapper generation. Load when integrating a REST/GraphQL API as a workspace tool
Capability-based provider system patterns. Load when creating providers or wiring module-provider integration
Full-stack WebSocket subscription development. Load when adding WS routes, topic services, or debugging WS data flows
Systematic Python type error resolution (mypy/pyright). Load when fixing backend type check failures
Type-first Python with Pydantic, Protocol, and discriminated unions. Load when writing models or enforcing typing
Systematic type error resolution for Python (mypy/pyright) and TypeScript (vue-tsc). Use when fixing type errors, resolving type check failures, or optimizing type imports.
| name | vue-frontend |
| description | Vue 3 Composition API + TypeScript patterns. Load when building components, composables, Pinia state, or reviewing Vue |
| user-invocable | false |
Standards and patterns for Vue 3 applications using Composition API, TypeScript, Pinia, and Vite — covering component design, state management, performance, data fetching, and testing.
Always use <script setup lang="ts"> syntax:
<script setup lang="ts">
import { ref, computed } from 'vue'
interface Props {
title: string
variant?: 'primary' | 'secondary'
}
const props = withDefaults(defineProps<Props>(), {
variant: 'primary',
})
const emit = defineEmits<{
close: []
submit: [value: string]
}>()
const inputValue = ref('')
const isValid = computed(() => inputValue.value.length > 0)
</script>
<template>
<div :class="['card', `card--${props.variant}`]">
<h2>{{ title }}</h2>
<slot />
</div>
</template>
<style scoped>
.card { /* component styles */ }
</style>
Component design rules:
withDefaults + defineProps<T>() for typed props with defaultsdefineEmits<T>() with named tuple syntax for typed eventsExtract shared logic into composables/ directory:
// composables/useFetch.ts
import { ref, type Ref } from 'vue'
interface UseFetchReturn<T> {
data: Ref<T | null>
error: Ref<string | null>
loading: Ref<boolean>
execute: () => Promise<void>
}
export function useFetch<T>(url: string | Ref<string>): UseFetchReturn<T> {
const data = ref<T | null>(null) as Ref<T | null>
const error = ref<string | null>(null)
const loading = ref(false)
async function execute() {
loading.value = true
error.value = null
try {
const resolvedUrl = typeof url === 'string' ? url : url.value
const response = await fetch(resolvedUrl)
if (!response.ok) throw new Error(`HTTP ${response.status}`)
data.value = await response.json()
} catch (e) {
error.value = e instanceof Error ? e.message : 'Unknown error'
} finally {
loading.value = false
}
}
return { data, error, loading, execute }
}
Composable rules:
use (e.g., useFetch, useAuth, useWebSocket)onUnmounted or watch cleanup callbackswatch / watchEffect with precise dependency listsprovide / inject sparingly for deep dependency injectionStructure stores by domain, not by data shape:
// stores/orders.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useOrderStore = defineStore('orders', () => {
// State
const orders = ref<Order[]>([])
const loading = ref(false)
// Getters (computed)
const pendingOrders = computed(() =>
orders.value.filter(o => o.status === 'pending')
)
const orderCount = computed(() => orders.value.length)
// Actions
async function fetchOrders() {
loading.value = true
try {
orders.value = await orderApi.getAll()
} finally {
loading.value = false
}
}
function addOrder(order: Order) {
orders.value.push(order)
}
return { orders, loading, pendingOrders, orderCount, fetchOrders, addOrder }
})
State management rules:
ref() for simple state, reactive() only for complex nested objectscomputed() for all derived state — never derive in templates| Technique | When to Use |
|---|---|
defineAsyncComponent | Lazy-load heavy components not needed on initial render |
v-once | Static content that never changes |
v-memo | List items where re-render is expensive and deps are known |
shallowRef / shallowReactive | Large objects where deep reactivity is unnecessary |
<Suspense> | Async component loading with fallback UI |
computed over watch | When derived value is sufficient (no side effects needed) |
Lazy loading example:
import { defineAsyncComponent } from 'vue'
const HeavyChart = defineAsyncComponent(() =>
import('./components/HeavyChart.vue')
)
Avoid these performance traps:
computed valuesv-if + v-for on the same element (use computed filtered list)key on v-for listsimport { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
component: () => import('./views/HomeView.vue'), // code splitting
},
{
path: '/trading',
component: () => import('./views/TradingView.vue'),
meta: { requiresAuth: true },
},
],
})
// Navigation guard
router.beforeEach((to) => {
if (to.meta.requiresAuth && !isAuthenticated()) {
return { path: '/login' }
}
})
Routing rules:
meta fields for route metadata (auth, breadcrumbs)useRoute() and useRouter() in <script setup> — never this.$route<script setup lang="ts">
import { ref } from 'vue'
const email = ref('')
const emailError = ref<string | null>(null)
function validateEmail() {
if (!email.value) {
emailError.value = 'Email is required'
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email.value)) {
emailError.value = 'Invalid email format'
} else {
emailError.value = null
}
}
function handleSubmit() {
validateEmail()
if (emailError.value) return
// submit logic
}
</script>
<template>
<form @submit.prevent="handleSubmit" novalidate>
<label for="email">Email</label>
<input
id="email"
v-model="email"
type="email"
autocomplete="email"
:aria-invalid="!!emailError"
:aria-describedby="emailError ? 'email-error' : undefined"
@blur="validateEmail"
/>
<p v-if="emailError" id="email-error" role="alert">
{{ emailError }}
</p>
<button type="submit">Submit</button>
</form>
</template>
Form rules:
v-model for controlled bindingsfor / aria-describedby// Global error handler (main.ts)
app.config.errorHandler = (err, instance, info) => {
console.error('Vue error:', err, info)
// report to error tracking service
}
Component-level:
onErrorCaptured for local error boundariestry/catch with user-friendly messagesstrict: true in tsconfig.jsondefineProps<T>() — never defineProps({}) with runtime validationref<string>(''), ref<Order | null>(null)defineEmits<{ update: [value: string] }>()type imports for interfaces: import type { Order } from '@/types'useFetch<T>(url)<style scoped> for component styles — prevents leakage<script setup>v-html without sanitization — XSS vector; sanitize rigorously or avoidthis.$refs in setup — Use template refs with ref<HTMLElement | null>(null)computed when no side effects are needed