๐Ÿ”Ÿ 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

๐Ÿš€ Viteย 7.0 โ€“ A Minimalist, High-Performance Milestone
01/07/2025 โ€” Nathan Cole
Vite 7 is here with a new Rust-powered bundler (Rolldown), improved browser targeting, and updated Node.js requirements. Explore whatโ€™s new, including ViteConf 2025, Vite DevTools, and enhanced plugin support.
๐Ÿง  Animate Like a Pro: Build a Modern Scroll-Triggered Website with GSAP
30/06/2025 โ€” James Carter
Learn how to create a visually rich, scroll-animated website using GSAP and ScrollTrigger. This step-by-step guide will help you master modern web animations, parallax effects, and responsive interactivity.
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.