Best Practices

πŸ”Ÿ Best Practices for Clean & Scalable Vue Components (with Code Examples)

Learn 10 essential tips for writing clean, scalable, and maintainable Vue 3 components using the Composition API, TypeScript, and modern patterns. Each tip comes with real-world code examples to help you level up your Vue skills.

James Carter
Author
Jul 7, 2025
Published
5 min
Read time
πŸ”Ÿ Best Practices for Clean & Scalable Vue Components (with Code Examples)
πŸ”Ÿ Best Practices for Clean & Scalable Vue Components (with Code Examples)

1. βœ… Use TypeScript for Props & Emits

Why: Ensures type-safety and better DX when consuming or editing the component.

<script setup lang="ts">
interface User {
  id: number
  name: string
}

const props = defineProps<{
  user: User
}>()

const emit = defineEmits<{
  (e: 'select', userId: number): void
}>()
</script>

2. 🧠 Group Logic with Composition API & <script setup>

Why: Clean and structured logic by purpose, not by options.

<script setup lang="ts">
import { ref, computed } from 'vue'

const count = ref(0)

const double = computed(() => count.value * 2)

function increment() {
  count.value++
}
</script>

<template>
  <button @click="increment">+1</button>
  <p>Double: {{ double }}</p>
</template>

3. ♻️ Extract Reusable Logic into Composables

Why: Keeps components focused and logic shareable.

// composables/useCounter.ts
import { ref } from 'vue'

export function useCounter() {
  const count = ref(0)
  const increment = () => count.value++

  return { count, increment }
}
<!-- MyCounter.vue -->
<script setup lang="ts">
import { useCounter } from '@/composables/useCounter'

const { count, increment } = useCounter()
</script>

<template>
  <button @click="increment">Count: {{ count }}</button>
</template>

4. 🧩 Keep Components Small & Single-Purpose

Why: Improves maintainability and testability.

<!-- AddTodo.vue -->
<script setup lang="ts">
import { ref } from 'vue'

const newTask = ref('')
const emit = defineEmits(['add'])

function submit() {
  if (newTask.value) emit('add', newTask.value)
  newTask.value = ''
}
</script>

<template>
  <input v-model="newTask" placeholder="New task" />
  <button @click="submit">Add</button>
</template>

5. 🧾 Use Interfaces for Local State

Why: Improves type safety and readability.

interface Product {
  id: number
  name: string
  price: number
}

const product = ref<Product>({
  id: 1,
  name: 'T-shirt',
  price: 19.99,
})

6. πŸ“Š Use Computed Properties for Derived State

Why: Keeps templates clean and makes values reactive and cacheable.

const price = ref(100)
const discount = ref(0.2)

const discountedPrice = computed(() => price.value * (1 - discount.value))
<p>Final Price: {{ discountedPrice }}</p>

7. 🌐 Handle Async Operations with States

Why: Avoids blank UI, improves error handling and UX.

const data = ref(null)
const loading = ref(false)
const error = ref<string | null>(null)

async function fetchData() {
  loading.value = true
  error.value = null
  try {
    const res = await fetch('/api/data')
    data.value = await res.json()
  } catch (e: any) {
    error.value = e.message
  } finally {
    loading.value = false
  }
}

8. πŸ’Ό Use Typed Slots for Flexibility & Clarity

Why: Makes slots powerful and predictable.

<!-- BaseList.vue -->
<script setup lang="ts">
defineProps<{
  items: Array<{ id: number; name: string }>
}>()
</script>

<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <slot :item="item">{{ item.name }}</slot>
    </li>
  </ul>
</template>
<!-- App.vue -->
<BaseList :items="products">
  <template #default="{ item }">
    <strong>{{ item.name }}</strong>
  </template>
</BaseList>

9. 🎨 Use CSS Variables for Theming

Why: Simplifies customization and light/dark theme support.

<template>
  <div class="card">Welcome to iCreatorStudio</div>
</template>

<style scoped>
.card {
  padding: 1rem;
  background-color: var(--card-bg);
  color: var(--card-text);
  border-radius: 0.5rem;
}
</style>
/* Global styles */
:root {
  --card-bg: #fff;
  --card-text: #333;
}

.dark-theme {
  --card-bg: #1e1e1e;
  --card-text: #eee;
}

10. πŸ“š Document Props, Emits, Slots with JSDoc

Why: Boosts dev experience in IDEs and helps future maintainers.

/**
 * @prop user - A user object with id and name
 */
const props = defineProps<{
  user: { id: number; name: string }
}>()

/**
 * @emits delete - Triggers when user is to be deleted
 */
const emit = defineEmits<{
  (e: 'delete', id: number): void
}>()

πŸ’‘ Final Thoughts

Following these 10 tips will help your Vue components stay:

  • πŸ” Reusable

  • 🧼 Clean

  • πŸ› οΈ Maintainable

  • πŸ’‘ Developer-friendly

Whether you're building a simple widget or a complex dashboard for a client, these small improvements compound into big results. At iCreatorStudio, we believe in crafting products that are polished both on the outside (UI) and inside (code).

Share this article

Help others discover this content by sharing it on your favorite platforms.

Stay Updated

Never Miss an Update

Subscribe to our newsletter and get the latest articles, tutorials, and product updates delivered straight to your inbox.

No spam, unsubscribe at any time.