Riad Kilani
  • Bio
  • Portfolio
  • Blog
  • Contact
  • Accessibility
  • Case Studies
  • CSS
  • Design
  • Front-End
  • HTML
  • JavaScript
  • News
  • Productivity
  • Random Thoughts
  • SEO
  • Themes
  • Trends
  • Tutorials
  • TypeScript
  • TypeSCript From The Ground Up
  • UX Engineering
  • Web Development
  • Wordpress
Home » Front-End » 10 Vue.js Tips & Tricks I Wish I Knew Sooner (Vue 3 Edition)

10 Vue.js Tips & Tricks I Wish I Knew Sooner (Vue 3 Edition)

August 29, 2025
Light abstract background with a stylized Vue “V” mark, minimal dashboard UI accents, and the title “10 Vue.js Tips & Tricks (Vue 3 Edition)”.

Level up your Vue 3 apps with ten practical tips—from script setup and smart reactivity to Teleport, Suspense, async components, and performance wins. Clear examples you can drop right into your project.

If you’ve shipped even one Vue app, you know the difference between “it works” and “it feels fast and clean.” These ten tips are the things I wish someone had told me when I moved full-time to Vue 3. They’re simple, battle-tested, and copy-paste friendly.

New to the bigger picture? Read my overview of how front-end evolved: Evolution of Front-End Development (2010–2025).

Table of contents

  • Use script setup to cut boilerplate
  • Type your props & emits (even without a full TS migration)
  • Reactivity: ref vs reactive—and when to use toRef(s)
  • Computed setters for two-way derived state
  • Watchers: watch vs watchEffect and common pitfalls
  • Async components + route-level code-splitting
  • Keep overlays sane with <Teleport>
  • Make loading feel instant with <Suspense>
  • Extract logic into composables
  • Quick performance wins that add up

1) Use script setup to cut boilerplate

The script setup syntax removes ceremony and gives you direct template access without return.

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

const props = defineProps<{ initial?: number }>()
const count = ref(props.initial ?? 0)
const double = computed(() => count.value * 2)
const increment = () => count.value++
</script>

<template>
  <button @click="increment">
    Count: {{ count }} (x2: {{ double }})
  </button>
</template>

Level up your editor speed too: Emmet Tips & Tricks for Beginners (That You’ll Actually Use).

2) Type your props & emits (even without a full TS migration)

You don’t need a full TypeScript stack to reap benefits. Minimal types stop bugs early and provide editor autocompletion.

<script setup lang="ts">
const props = defineProps<{
  modelValue: string
  max?: number
}>()

const emit = defineEmits<{
  (e: 'update:modelValue', value: string): void
  (e: 'reach-max'): void
}>()

function onInput(val: string) {
  if (props.max && val.length > props.max) {
    emit('reach-max')
    return
  }
  emit('update:modelValue', val)
}
</script>

3) Reactivity: ref vs reactive—and when to use toRef(s)

Use ref for primitives (number, string, boolean) and when you plan to replace the value. Use reactive for objects you mutate deeply. Use toRef/toRefs to create reactive “pointers” to props or fields, avoiding accidental de-reactivity with destructuring.

import { reactive, toRef, toRefs } from 'vue'

const state = reactive({ user: { name: 'Sara', age: 17 }, loading: false })
const loading = toRef(state, 'loading')        // direct link to state.loading
const { user } = toRefs(state)                 // keep reactivity when destructuring

If JavaScript scope trips you up, this primer will help: Understanding JavaScript Scope: A Beginner’s Guide.

4) Computed setters for two-way derived state

computed isn’t just for getters. A setter lets you sync derived state elegantly.

import { ref, computed } from 'vue'

const first = ref('Riad')
const last = ref('Kilani')

const fullName = computed({
  get: () => `${first.value} ${last.value}`,
  set: (val: string) => {
    const [f, ...rest] = val.split(' ')
    first.value = f
    last.value = rest.join(' ')
  }
})

// fullName.value = 'Sara Kilani' updates both first/last

5) Watchers: watch vs watchEffect and common pitfalls

watch(source, cb) runs when the source changes (great for params or specific refs). watchEffect(cb) tracks everything used inside the callback (great for quick side effects).

import { watch, watchEffect } from 'vue'
import { useRoute } from 'vue-router'
const route = useRoute()

watch(() => route.params.id, (id) => {
  // fetch user when id changes
}, { immediate: true })

watchEffect(() => {
  // runs when any accessed reactive value changes
  console.log('Current path:', route.path)
})

Working with async side effects? Start here: JavaScript Promise: What It Is and How to Use It.

6) Async components + route-level code-splitting

Make big chunks lazy. Users shouldn’t download admin charts on the landing page. With Vue Router, dynamically import views so each route becomes its own chunk.

// LazyUserCard.ts
import { defineAsyncComponent } from 'vue'
export const UserCard = defineAsyncComponent(() => import('./UserCard.vue'))
<!-- Somewhere.vue -->
<script setup>
import { UserCard } from './LazyUserCard'
</script>

<template>
  <UserCard />
</template>
{
  path: '/reports',
  component: () => import('@/views/ReportsView.vue')
}

7) Keep overlays sane with <Teleport>

Modals, toasts, and dropdowns are easier when you render them near <body> to bypass overflow and z-index battles.

<template>
  <button @click="open = true">Open modal</button>

  <Teleport to="body">
    <div v-if="open" class="backdrop" @click="open = false">
      <div class="modal" @click.stop>
        <h3>Settings</h3>
        <button @click="open = false">Close</button>
      </div>
    </div>
  </Teleport>
</template>

<script setup>
import { ref } from 'vue'
const open = ref(false)
</script>

8) Make loading feel instant with <Suspense>

Show a graceful fallback while async components or data are resolving. Pair with route-level async data or composables that return promises.

<script setup>
import { defineAsyncComponent } from 'vue'
const UserProfile = defineAsyncComponent(() => import('./UserProfile.vue'))
</script>

<template>
  <Suspense>
    <template #default>
      <UserProfile />
    </template>
    <template #fallback>
      <div aria-busy="true">Loading profile…</div>
    </template>
  </Suspense>
</template>

9) Extract logic into composables (your future self will thank you)

Move reusable logic (fetching, debounce, feature flags) into src/composables. Keep UI in components, stateful logic in composables, and make them framework-agnostic when possible.

// src/composables/useDebounce.ts
import { ref, watch, type Ref } from 'vue'

export function useDebounce<T>(source: Ref<T>, ms = 300) {
  const debounced = ref(source.value) as Ref<T>
  let t: ReturnType<typeof setTimeout> | null = null

  watch(source, (val) => {
    if (t) clearTimeout(t)
    t = setTimeout(() => { debounced.value = val }, ms)
  }, { immediate: true })

  return debounced
}
// in a component
const query = ref('')
const debouncedQuery = useDebounce(query, 400)
// watch debouncedQuery to trigger API calls less often

10) Quick performance wins that add up

  • Use key correctly when rendering lists; avoid using array index as the key if items can move.
  • Cache heavy computations in computed rather than recalculating in templates.
  • v-once for content that never changes.
  • Split big components into smaller ones and lazy-load what you can.
  • Throttle expensive watchers and API calls (combine with the useDebounce composable above).
<li v-for="user in users" :key="user.id">{{ user.name }}</li>

For semantic + a11y gains that also help SEO, see 10 Must-Use HTML Tags in 2025, and stay current with New CSS Features to Know in 2025.

Related Reading

  • Understanding JavaScript Scope: A Beginner’s Guide
  • JavaScript Promise: What It Is and How to Use It
  • 10 Must-Use HTML Tags in 2025
  • New CSS Features to Know in 2025
  • Evolution of Front-End Development (2010–2025)
  • From Portfolio to Platform: Building a SPA + WordPress Ecosystem

Curious how I run this stack in the real world? Here’s the behind-the-scenes: From Portfolio to Platform: Building a SPA + WordPress Ecosystem.

If you found this useful, share it with a teammate who’s currently fighting a modal or an infinite spinner. And if you want more deep dives (Pinia patterns? Advanced Suspense? Real-world perf budgets?), drop a comment—I read them all.

FAQ

Is Vue 3 required for these tips?

Most are Vue 3-centric (e.g., script setup, <Suspense>). If you’re on Vue 2, consider the migration path or the composition-api plugin for some patterns.

Do I need TypeScript?

No, but adding light types to props/emits improves DX even if the rest is JS.

What about state management?

Composables handle a lot, but for cross-app state and devtools, Pinia is ergonomic and pairs well with Vue 3.

How do I keep bundle size down?

Async components, route-level code splitting, and avoiding large dependencies on initial routes.

When should I use reactive vs ref?

ref for primitives or frequently replaced values; reactive for objects you mutate. Use toRef(s) to keep reactivity when destructuring.

Tip: Set the post’s Slug to vuejs-tips-and-tricks, enter the Meta description in your SEO plugin, and assign Categories: Front-End, Vue.js. Tags: Vue 3, Composition API, script setup, Teleport, Suspense, performance, async components.

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

← Previous Post Building a Block Theme in 2025
Next Post → Up and Running with Vue.js (Fast Start for 2025)

Categories

  • Accessibility
  • Case Studies
  • CSS
  • Design
  • Front-End
  • HTML
  • JavaScript
  • News
  • Productivity
  • Random Thoughts
  • SEO
  • Themes
  • Trends
  • Tutorials
  • TypeScript
  • TypeSCript From The Ground Up
  • UX Engineering
  • Web Development
  • Wordpress

Recent Posts

  • Native CSS Is Quietly Replacing Sass, But It Isn’t Replacing the “Need” for Sass
  • Everyday Types Explained (From the Ground Up)
  • 2026 CSS Features You Must Know (Shipped Late 2025–Now)
  • 60 JavaScript Projects in 60 Days
  • JavaScript vs TypeScript: What Actually Changes

Tags

accessibility accessible web design ADA compliance async components Career Journey cascade layers code splitting composables composition api computed properties container queries css Design Inspiration Design Systems disability access File Organization Front-End Development Frontend frontend development immutability javascript JavaScript reducers lazy loading Material Design Modern CSS performance Personal Growth react React useReducer Redux Resume screen readers seo Suspense Teleport TypeScript UI/UX UI Engineering UX UX Engineering Vue Router WCAG web accessibility Web Development Web Performance

Riad Kilani Front-End Developer

© 2026 Riad Kilani. All rights reserved.