vue-performance
SkillDev toolsVue Best Practices
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the vue-performance skill
What this skill tells your AI
The instructions your AI receives, as published by caido-community/shift in .agents/skills/vue-performance/SKILL.md and read by ahel’s review.
Update Optimizations
Props Stability
- Keep props passed to child components as stable as possible.
- Instead of passing reactive IDs and comparing inside child:
<!-- Bad: Every ListItem updates when activeId changes -->
<ListItem
v-for="item in list"
:id="item.id"
:active-id="activeId" />
- Compute the derived value in the parent and pass it directly:
<!-- Good: Only items whose active status changed will update -->
<ListItem
v-for="item in list"
:id="item.id"
:active="item.id === activeId" />
Computed Stability
- Computed properties only trigger effects when their value changes.
- Avoid returning new objects from computed properties when the underlying data hasn't changed:
// Bad: Creates new object every time, always triggers updates
const computedObj = computed(() => {
return {
isEven: count.value % 2 === 0
}
})
// Good: Returns old value if nothing changed
const computedObj = computed((oldValue) => {
const newValue = {
isEven: count.value % 2 === 0
}
if (oldValue && oldValue.isEven === newValue.isEven) {
return oldValue
}
return newValue
})
- Always perform the full computation before comparing and returning the old value to ensure dependencies are collected.
Reduce Reactivity Overhead for Large Immutable Structures
- For large arrays of deeply nested objects, use
shallowRef()andshallowReactive()to opt-out of deep reactivity. - Shallow APIs create state reactive only at the root level, keeping nested property access fast.
- When using shallow reactivity, treat nested objects as immutable and trigger updates by replacing the root state:
const shallowArray = shallowRef([/* big list of deep objects */])
// Bad: Won't trigger updates
shallowArray.value.push(newObject)
shallowArray.value[0].foo = 1
// Good: Replace the root state
shallowArray.value = [...shallowArray.value, newObject]
shallowArray.value = [
{ ...shallowArray.value[0], foo: 1 },
...shallowArray.value.slice(1)
]
MaybeRefOrGetter with toValue
- Use
MaybeRefOrGetter<T>type for flexible function parameters that can accept refs, getters, or plain values. - Use
toValue()to extract the actual value fromMaybeRefOrGetterparameters:
- Handle Async Operations with Error and Loading States
Always handle every possible state for data fetching or async logic, using separate components for each state: loading, success, error, and empty.
Example:
<script setup lang="ts">
import { ref } from "vue";
import type { User } from "@/types";
const user = ref<User | undefined>(undefined);
const loading = ref(true);
const error = ref<Error | undefined>(undefined);
async function fetchUserData(userId: string) {
loading.value = true;
error.value = undefined;
try {
const response = await fetch(`/api/users/${userId}`);
if (!response.ok) throw new Error("Failed to fetch user data");
user.value = await response.json();
} catch (e) {
error.value = e instanceof Error ? e : new Error("Unknown error");
user.value = undefined;
} finally {
loading.value = false;
}
}
</script>
<template>
<div>
<LoadingSpinner v-if="loading" />
<ErrorMessage v-else-if="error !== undefined" :message="error.message" @retry="() => fetchUserData('replace-id')" />
<UserProfile v-else-if="user !== undefined" :user="user" />
<EmptyState v-else message="No user data available" />
</div>
</template>
This ensures each state (loading, error, data, empty) is handled explicitly and the UI never displays an inconsistent result.
Signals
- GitHub stars
- 57
- Forks
- 13
- Last commit
- Jul 2026
Advanced
- Catalog kind
- skill
- Gateway key
vue-performance- Source
- github.com/caido-community/shift