Ya sabes montar un componente Vue. La pregunta es si seguirá teniendo sentido dentro de tres meses, cuando otra persona (o tú mismo, sin memoria) abra el archivo. Las buenas prácticas en Vue no son reglas de estilo: son lo que separa un componente que entiendes de un vistazo de uno que necesitas depurar cada vez.

script setup no es azúcar sintáctico, es el estándar
Si todavía escribes setup() devolviendo un objeto, detente. <script setup> es el estándar moderno: menos boilerplate, mejor inferencia de tipos, y todo lo que declaras en el nivel superior ya está expuesto al template. Composition API con script setup permite agrupar la lógica por tema, en lugar de repartirla entre data, methods y computed, como ocurre en Options API. Lees de arriba abajo y entiendes lo que hace el componente.
Un componente grande es un bug esperando ocurrir
Un SFC de 400 líneas con seis responsabilidades es imposible de probar y horrible de reutilizar. Divídelo. Si una parte del template tiene estado propio y un nombre propio, es un componente. La regla práctica: si necesitas desplazarte por la pantalla para ver todo el <script>, probablemente ya te pasaste del límite. Un componente pequeño también hace que la reactividad sea más predecible: menos estado en un mismo lugar y menos efectos secundarios inesperados.
Props tipadas y emits explícitos
Las props sin tipo son un contrato roto esperando causar problemas. Usa la firma genérica:
ts const props = defineProps<{ userId: number; active?: boolean }>() const emit = defineEmits<{ (e: 'update', value: string): void }>()
Ahora el editor te avisa cuando alguien pasa userId como string, y el emit se puede rastrear: nada de eventos fantasma cuyo origen nadie conoce. Las props son de solo lectura: mutar una prop dentro del hijo es el clásico bug que desaparece y vuelve a aparecer. Emites el evento y el padre decide.
computed en lugar de watcher (casi siempre)
Este es el error más común de quienes vienen de otros frameworks. ¿Necesitas un valor derivado de otro? computed. Está cacheado, es declarativo y se vuelve a evaluar automáticamente cuando cambia la dependencia:
ts
const fullName = computed(() => ${first.value} ${last.value})
watcher es para efectos secundarios: lanzar un fetch, sincronizar con localStorage o registrar un log. Si tu watch termina asignando un valor a otra variable reactiva, casi siempre era un computed disfrazado. Tener demasiados watchers es una señal de que parte del estado debería derivarse.
Pinia para el estado global, y nada más
El estado que comparten dos componentes alejados debe ir a una store. Pinia es el estándar actual: tipado, sin mutations y con state/getters/actions claros. Pero cuidado con exagerar en la otra dirección: no todo es global. El estado que solo usan un componente y sus hijos debe quedarse local o pasar mediante props. Una store llena de cosas que eran locales se convierte en un cubo de estado que nadie entiende. Lo global es para lo que realmente es global: el usuario autenticado, el tema o el carrito.
Una key estable en v-for no es un detalle
:key le indica a Vue qué elemento es cuál entre un renderizado y otro. Usar el índice del array como key es pedir problemas: reordenas, filtras o eliminas un elemento y Vue reutiliza el nodo equivocado; el input termina con un valor cambiado o el componente conserva su estado en el lugar incorrecto. Usa un id estable y único del propio dato:
html
El índice solo sirve para una lista estática que nunca cambia de orden. Ante la duda, usa un id.
Nada de esto es difícil. Son hábitos. El componente que sigue todas estas prácticas no es más inteligente: simplemente es más difícil de romper, y eso es exactamente lo que quieres en un código que va a durar.

