Vue Best Practices

Practical Vue best practices for building components that are easier to read, test, reuse, and maintain.

Gabriel's avatar
Gabriel
Vue Best Practices

You already know how to build a Vue component. The question is whether it will still make sense three months from now, when someone else—or you, with no memory of it—opens the file. Best practices in Vue aren't style rules; they're what separates a component you understand at a glance from one that needs to be debugged every time.

Opening image representing well-organized Vue code: small, neatly connected, predictable components without a generic code-screen cliché.
Opening image representing well-organized Vue code: small, neatly connected, predictable components without a generic code-screen cliché.

script setup isn't syntactic sugar—it's the default

If you're still writing setup() and returning an object, stop. <script setup> is the modern default: less boilerplate, better type inference, and everything declared at the top level is already exposed to the template. The Composition API with script setup keeps logic grouped by topic instead of scattered across data, methods, and computed as in the Options API. You can read from top to bottom and understand what the component does.

A large component is a bug waiting to happen

A 400-line SFC with six responsibilities is impossible to test and terrible to reuse. Break it up. If a section of the template has its own state and its own name, it should probably be a component. A practical rule: if you have to scroll to see the entire <script> section, you've probably gone too far. Small components also make reactivity more predictable—less state in one place means fewer surprising side effects.

Typed props and explicit emits

Untyped props are a broken contract waiting to happen. Use the generic signature:

ts const props = defineProps<{ userId: number; active?: boolean }>() const emit = defineEmits<{ (e: 'update', value: string): void }>()

Now your editor warns you when someone passes userId as a string, and emit is traceable—no phantom events that no one knows where they came from. Props are read-only: mutating a prop inside the child is the classic bug that disappears and comes back. Emit the event; let the parent decide.

Use computed instead of watch (almost always)

This is the most common mistake among people coming from other frameworks. Need a value derived from another one? Use computed. It's cached, declarative, and recalculates automatically when its dependency changes:

ts const fullName = computed(() => ${first.value} ${last.value})

Use a watcher for side effects—triggering a fetch, synchronizing with localStorage, or logging. If your watch ends by assigning a value to another reactive variable, it was almost certainly a computed in disguise. Too many watchers are a sign that some state should be derived instead.

Pinia for global state—and only global state

State shared by two distant components belongs in a store. Pinia is the standard today: typed, without mutations, and with clean state/getters/actions. But be careful not to overcorrect—not everything is global. State used only by one component and its children should stay local or flow down through props. A store filled with things that should have been local becomes a bucket of state no one understands. Global is for what is genuinely global: the logged-in user, the theme, or the shopping cart.

A stable key in v-for isn't a minor detail

:key tells Vue which item is which between renders. Using the array index as a key is asking for trouble: reorder, filter, or remove an item, and Vue may reuse the wrong node—an input with the wrong value or a component with its state stuck in the wrong place. Use a stable, unique ID from the data itself:

html

  • {{ item.name }}
  • An index is only suitable for a static list that never changes order. When in doubt, use an ID.

    None of this is difficult. These are habits. A component that follows all of them isn't smarter—it's simply harder to get wrong, and that's exactly what you want in code that will last.

    Did you enjoy this article?

    Share it with your friends and help spread knowledge!