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

25/06/2025 - James Carter

πŸ”Ÿ 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 post.
newsletter

Stay Updated with the Latest News

Join our newsletter to stay informed about upcoming updates,
new releases, special offers, and more exciting products.

Don't miss this

You might also like

Double Trouble: Why Two Major Nuxt Releases Are on the Horizon
25/06/2025 β€” James Carter
Nuxt is gearing up for two major releases: Nuxt 4 and Nuxt 5. Discover what’s changing, why it matters, and how to prepare for the future of the Nuxt ecosystem.
Why We Still Love Vuetify in 2025 – And What’s New in the May Update
11/06/2025 β€” James Carter
Discover why Vuetify continues to be our go-to Vue.js framework in 2025. From the powerful MCP release to the revamped VDateInput and new VColorInput, the May update delivers serious upgrades for developers.
The Best AI Tool for Developers and How It Helps
04/06/2025 β€” Nathan Cole
AI
Discover the best AI tool for developers β€” GitHub Copilot. Learn how it boosts productivity, reduces bugs, and transforms the way developers write code.