codejump

Interview questionsVue

Vue interview questions and answers

30 Vue questions picked from Codejump's quizzes, from the basics a junior is expected to know to the details that trip up senior candidates. Each one comes with its answer and an explanation — try to answer before you open it.

Junior level Vue questions

  1. 1.Which Composition API hook replaces the Options API mounted hook?

    javascript
    import { onMounted, ref } from 'vue'
    
    const el = ref(null)
    
    onMounted(() => {
      console.log('Component is mounted!')
      console.log(el.value) // DOM element is accessible here
    })
    • A.beforeMount()
    • B.useMounted()
    • C.onMounted()
    • D.mounted()
    Show answer

    Answer: C. onMounted()

    In the Composition API, lifecycle hooks are functions imported from Vue and called inside setup(). The mounted option becomes onMounted(). The complete mapping from Options API to Composition API:
    • beforeCreate / created → use setup() directly
    • beforeMount → onBeforeMount()
    • mounted → onMounted()
    • beforeUpdate → onBeforeUpdate()
    • updated → onUpdated()
    • beforeUnmount → onBeforeUnmount()
    • unmounted → onUnmounted()

    From the quiz ref & reactive

  2. 2.How do you create and use layouts in Nuxt 3?

    javascript
    <!-- layouts/default.vue — applied to all pages by default -->
    <template>
      <div>
        <AppHeader />
        <main>
          <slot /> <!-- Page content renders here -->
        </main>
        <AppFooter />
      </div>
    </template>
    
    <!-- layouts/admin.vue — custom layout for admin pages -->
    <template>
      <div class="admin-shell">
        <AdminSidebar />
        <main>
          <slot />
        </main>
      </div>
    </template>
    
    <!-- pages/dashboard.vue — uses the admin layout -->
    <script setup>
    definePageMeta({
      layout: 'admin'
    })
    </script>
    
    <template>
      <div>
        <h1>Dashboard</h1>
      </div>
    </template>
    
    <!-- pages/login.vue — no layout -->
    <script setup>
    definePageMeta({
      layout: false // Disable layout entirely
    })
    </script>
    • A.Wrap every page in a <Layout> component manually
    • B.Create .vue files in the layouts/ directory — use definePageMeta({ layout: 'name' }) in pages to select a layout
    • C.Configure layouts in nuxt.config.ts under the layouts key
    • D.Layouts are not supported in Nuxt 3 — use slot-based composition instead
    Show answer

    Answer: B. Create .vue files in the layouts/ directory — use definePageMeta({ layout: 'name' }) in pages to select a layout

    Layouts wrap page content with shared UI (navigation, footer, sidebar). The layouts/default.vue layout is applied automatically.

    From the quiz Nuxt Basics

  3. 3.What does the v-once directive do and when should you use it?

    javascript
    <template>
      <!-- Static content that never changes after initial render -->
      <h1 v-once>Welcome to {{ appName }}</h1>
      <!-- appName won't update even if the ref changes -->
    
      <!-- Static translations -->
      <footer v-once>
        <p>Version {{ version }}</p>
        <p>{{ copyright }}</p>
      </footer>
    
      <!-- Large static tree -->
      <div v-once>
        <ProductShowcase :product="initialProduct" />
        <!-- initialProduct changes won't affect this -->
      </div>
    </template>
    
    <!-- Vue 3.2+ alternative: v-memo for conditional memoization -->
    <template>
      <div v-for="item in list" :key="item.id"
        v-memo="[item.id, item.selected]">
        <!-- Only re-renders if item.id or item.selected changes -->
        <ExpensiveItemRenderer :item="item" />
      </div>
    </template>
    • A.It prevents the element from rendering until a condition is met
    • B.It limits the number of times a component can re-render to once per second
    • C.It renders the element/component once and skips future updates, treating it as static content
    • D.It is equivalent to v-if with a boolean that starts as true
    Show answer

    Answer: C. It renders the element/component once and skips future updates, treating it as static content

    v-once renders the element or component only once. On subsequent re-renders, Vue skips it entirely. Use it for truly static content.

    From the quiz v-memo & v-once

  4. 4.How do you access the current route's parameters in a component?

    javascript
    // Options API
    export default {
      mounted() {
        console.log(this.$route.params.id)    // Route param
        console.log(this.$route.query.search) // Query string
        console.log(this.$route.path)         // Current path
        console.log(this.$route.name)         // Route name
      }
    }
    
    // Composition API (recommended)
    import { useRoute } from 'vue-router'
    
    const route = useRoute()
    console.log(route.params.id)
    console.log(route.query.search)
    
    // Route definition for context:
    const routes = [
      { path: '/users/:id', component: UserProfile }
      //                ↑ This creates params.id
    ]
    • A.this.$params.id (Options API), useParams() (Composition API)
    • B.this.$router.params.id (Options API), useRouter().params.id (Composition API)
    • C.this.$route.params.id (Options API), useRoute().params.id (Composition API)
    • D.Only through props — route params are not directly accessible in components
    Show answer

    Answer: C. this.$route.params.id (Options API), useRoute().params.id (Composition API)

    Route parameters are accessed via the current route object. In Options API this is this.$route, in Composition API use useRoute().

    From the quiz Router Basics

  5. 5.How do you use a Pinia store in a Vue 3 component?

    javascript
    <script setup>
    import { useCounterStore } from '@/stores/counter'
    
    // Get the store instance
    const counter = useCounterStore()
    
    // Access state directly
    console.log(counter.count)
    
    // Access getters
    console.log(counter.double)
    
    // Call actions
    function handleClick() {
      counter.increment()
    }
    
    // $patch for bulk updates
    counter.$patch({ count: 10, name: 'Updated' })
    
    // $patch with function (for complex updates)
    counter.$patch((state) => {
      state.count++
      state.name = `Count: ${state.count}`
    })
    </script>
    
    <template>
      <p>Count: {{ counter.count }}</p>
      <p>Double: {{ counter.double }}</p>
      <button @click="counter.increment()">Increment</button>
    </template>
    • A.Import useCounterStore and call it anywhere in the component
    • B.Inject the store using inject('counterStore')
    • C.Import useCounterStore and call it inside setup() or <script setup>
    • D.Access via this.$store.counter in Options API components
    Show answer

    Answer: C. Import useCounterStore and call it inside setup() or <script setup>

    Call the store composable inside setup() to get the reactive store instance. The same instance is shared across all components.

    From the quiz Pinia Basics

  6. 6.Quelle est la différence clé entre une propriété computed et une method dans ce contexte ?

    javascript
    <script setup>
    import { ref, computed } from 'vue'
    
    const firstName = ref('Alice')
    const lastName = ref('Martin')
    
    // computed
    const fullNameComputed = computed(() => `${firstName.value} ${lastName.value}`)
    
    // method
    function fullNameMethod() {
      return `${firstName.value} ${lastName.value}`
    }
    </script>
    
    <template>
      <p>{{ fullNameComputed }}</p>
      <p>{{ fullNameMethod() }}</p>
    </template>
    • A.computed est mis en cache selon ses dépendances réactives — il ne recalcule que si firstName ou lastName change. fullNameMethod() est appelée à chaque rendu du composant
    • B.Les deux sont identiques — computed est juste une syntaxe alternative aux méthodes
    • C.computed ne peut pas accéder aux ref — seules les méthodes le peuvent
    • D.computed s'exécute au moment de l'import du composant, avant le rendu
    Show answer

    Answer: A. computed est mis en cache selon ses dépendances réactives — il ne recalcule que si firstName ou lastName change. fullNameMethod() est appelée à chaque rendu du composant

    Use computed when: the result depends on reactive state and is expensive to compute. Use method when: you need to trigger a side effect, or the function takes arguments.

    From the quiz Vue Fundamentals

  7. 7.How do you access a DOM element with a template ref in the Composition API?

    javascript
    <template>
      <input ref="inputEl" type="text" />
      <button @click="focusInput">Focus</button>
    </template>
    
    <script setup>
    import { ref, onMounted } from 'vue'
    
    const inputEl = ref(null)
    
    onMounted(() => {
      inputEl.value.focus() // Direct DOM access
    })
    
    function focusInput() {
      inputEl.value?.focus()
    }
    </script>
    • A.Declare const myEl = ref(null) and use ref="myEl" in the template
    • B.Declare const myEl = ref(null) and use v-ref="myEl" in the template
    • C.Use this.$refs.myEl inside onMounted
    • D.Use document.querySelector('#myEl') inside onMounted
    Show answer

    Answer: A. Declare const myEl = ref(null) and use ref="myEl" in the template

    To use template refs in the Composition API, declare a ref with null as initial value, then bind it to the element using ref="varName" in the template. The ref will be populated after the component mounts. The ref name in <script setup> must match the ref attribute value in the template.

    From the quiz ref & reactive

  8. 8.How does <NuxtLink> differ from a plain <a> tag?

    javascript
    <template>
      <!-- Internal navigation — client-side, no page reload -->
      <NuxtLink to="/about">About</NuxtLink>
    
      <!-- Named route -->
      <NuxtLink :to="{ name: 'users-id', params: { id: user.id } }">
        View Profile
      </NuxtLink>
    
      <!-- External link — renders a plain <a> with a full-page navigation.
           Nuxt adds rel="noopener noreferrer" automatically; it does NOT add
           target="_blank" — set that yourself if you want a new tab. -->
      <NuxtLink to="https://nuxt.com" external target="_blank">Nuxt Website</NuxtLink>
    
      <!-- Prefetch control — disable for rarely visited pages -->
      <NuxtLink to="/heavy-page" :prefetch="false">Heavy Page</NuxtLink>
    
      <!-- Custom active class -->
      <NuxtLink to="/" active-class="nav-active" exact-active-class="nav-exact">
        Home
      </NuxtLink>
    </template>
    • A.<NuxtLink> renders faster because it avoids the browser's default navigation
    • B.<NuxtLink> automatically adds rel="noopener" to external links
    • C.<NuxtLink> is Nuxt's enhanced router-link that performs client-side navigation and automatically prefetches linked pages
    • D.<NuxtLink> is identical to <RouterLink> — they are interchangeable
    Show answer

    Answer: C. <NuxtLink> is Nuxt's enhanced router-link that performs client-side navigation and automatically prefetches linked pages

    <NuxtLink> extends Vue Router's <RouterLink> with Nuxt-specific features: Key Nuxt-specific features:
    • Auto-prefetch: When a <NuxtLink> is visible, Nuxt prefetches the linked page's JavaScript chunk
    • External detection: recognises absolute URLs, renders them as a plain <a> with a full-page navigation, and adds rel="noopener noreferrer" (also applied whenever target="_blank" is set). It does not add target="_blank" itself.
    • No import needed: Auto-imported component

    From the quiz Nuxt Basics

  9. 9.How do you lazily load a Vue component so it only loads when needed?

    javascript
    import { defineAsyncComponent } from 'vue'
    
    // Basic async component
    const HeavyModal = defineAsyncComponent(
      () => import('./components/HeavyModal.vue')
    )
    
    // With loading and error states
    const AsyncTable = defineAsyncComponent({
      loader: () => import('./components/DataTable.vue'),
      loadingComponent: LoadingSpinner,
      errorComponent: ErrorDisplay,
      delay: 200,         // Show loading component after 200ms
      timeout: 10000,     // Show error component if not loaded in 10s
      onError(error, retry, fail, attempts) {
        if (attempts <= 3) {
          retry() // Retry up to 3 times on network error
        } else {
          fail()
        }
      }
    })
    • A.Use import() inside onMounted hook
    • B.Use v-lazy directive from Vue core
    • C.Add lazy: true to the component options
    • D.Use defineAsyncComponent(() => import('./MyComponent.vue'))
    Show answer

    Answer: D. Use defineAsyncComponent(() => import('./MyComponent.vue'))

    defineAsyncComponent creates a component that is only loaded when it's first rendered. Vite/webpack split it into a separate chunk.

    From the quiz v-memo & v-once

  10. 10.What is the primary advantage of using named routes?

    javascript
    // Route definition
    const routes = [
      { path: '/users/:id/profile', name: 'user-profile', component: UserProfile },
      { path: '/settings', name: 'settings', component: Settings }
    ]
    
    // ✅ Using named routes — resilient to URL changes
    router.push({ name: 'user-profile', params: { id: 42 } })
    
    <router-link :to="{ name: 'user-profile', params: { id: user.id } }">
      View Profile
    </router-link>
    
    // ❌ Hardcoded path — breaks if URL changes
    router.push(`/users/${id}/profile`)
    <router-link :to="`/users/${user.id}/profile`">View Profile</router-link>
    • A.Named routes are faster because Vue Router caches their matched components
    • B.Named routes automatically enable route-level code splitting
    • C.Named routes are required to use dynamic route parameters
    • D.Named routes allow navigation without hardcoding path strings, making URL refactoring easier
    Show answer

    Answer: D. Named routes allow navigation without hardcoding path strings, making URL refactoring easier

    Named routes decouple your navigation code from URL structure. If you change a URL, you only update the route definition — all router-link and router.push() calls using the name remain unchanged.

    From the quiz Router Basics

Mid level Vue questions

  1. 11.What defines a Vue 3 composable by convention?

    javascript
    // useCounter.js
    import { ref, computed } from 'vue'
    
    export function useCounter(initialValue = 0) {
      const count = ref(initialValue)
      const double = computed(() => count.value * 2)
    
      function increment() { count.value++ }
      function decrement() { count.value-- }
      function reset() { count.value = initialValue }
    
      return { count, double, increment, decrement, reset }
    }
    
    // Usage in any component — each component gets isolated state
    import { useCounter } from '@/composables/useCounter'
    
    const { count, double, increment } = useCounter(10)
    • A.Any function that returns an object with data, methods, and computed keys
    • B.A file that must be exported from a composables/ directory
    • C.A class that extends VueComposable from the Vue package
    • D.A function prefixed with use that encapsulates stateful reactive logic using Composition API
    Show answer

    Answer: D. A function prefixed with use that encapsulates stateful reactive logic using Composition API

    A composable is a function that uses Vue's Composition API to encapsulate and reuse stateful logic. By convention, composable names start with use. Each call to a composable creates its own isolated reactive state.

    From the quiz watch & Composables

  2. 12.What are the three types of middleware in Nuxt and when do they run?

    javascript
    // 1. Route middleware — runs on client during navigation
    // middleware/auth.ts
    export default defineNuxtRouteMiddleware((to, from) => {
      const { isAuthenticated } = useAuth()
    
      if (!isAuthenticated.value && to.meta.requiresAuth) {
        return navigateTo('/login', { replace: true })
      }
    })
    
    // Used in a page:
    definePageMeta({ middleware: 'auth' })
    
    // 2. Global route middleware — runs on ALL navigations
    // middleware/analytics.global.ts — .global suffix makes it run everywhere
    export default defineNuxtRouteMiddleware((to) => {
      useTrackPageView(to.fullPath)
    })
    
    // 3. Server middleware — runs on every HTTP request (Nitro)
    // server/middleware/logging.ts
    export default defineEventHandler((event) => {
      console.log(`[${new Date().toISOString()}] ${getMethod(event)} ${getRequestURL(event)}`)
      // No return = passes to next handler
      // return something = short-circuits the request
    })
    • A.Route middleware (navigation-time, client-side), Server middleware (every request, server-side), and Global route middleware (runs on every navigation)
    • B.Server middleware (server-side), client middleware (client-side), and universal middleware (both sides)
    • C.Page middleware (per-page), app middleware (app-wide), and plugin middleware (plugin execution)
    • D.Auth middleware, analytics middleware, and redirect middleware — categorized by purpose
    Show answer

    Answer: A. Route middleware (navigation-time, client-side), Server middleware (every request, server-side), and Global route middleware (runs on every navigation)

    Nuxt has two distinct middleware systems:

    From the quiz SSR & Data Fetching

  3. 13.What does v-memo do and when should you use it?

    javascript
    <template>
      <!-- v-memo is particularly powerful in v-for with complex items -->
      <div
        v-for="item in largeList"
        :key="item.id"
        v-memo="[item.id, item.selected, item.value]"
      >
        <!-- This entire subtree is skipped if item.id, item.selected,
             and item.value are all the same as last render -->
        <ComplexItemRenderer :item="item" />
        <div class="metadata">
          <span>ID: {{ item.id }}</span>
          <span>Value: {{ item.value }}</span>
          <!-- ... 50 more bindings -->
        </div>
      </div>
    
      <!-- Without v-memo: ALL items re-render when any item changes -->
      <!-- With v-memo: ONLY items with changed dependency values re-render -->
    </template>
    • A.It skips re-rendering a subtree if the provided array of values hasn't changed since the last render
    • B.It memoizes the component props between renders, preventing prop object creation
    • C.It caches the computed value of an expression in the template
    • D.It is equivalent to v-once but with a condition
    Show answer

    Answer: A. It skips re-rendering a subtree if the provided array of values hasn't changed since the last render

    v-memo takes an array of dependency values. If they haven't changed since the last render, Vue skips the entire subtree diff — similar to React.memo for children.

    From the quiz shallowRef & markRaw

  4. 14.How do you add a navigation guard specific to one route?

    javascript
    // Route-level guard — in route definition
    const routes = [
      {
        path: '/admin',
        component: AdminPanel,
        beforeEnter: [requireAdmin, logAccess], // Array of guards
        // or a single function:
        beforeEnter: (to, from) => {
          if (!store.isAdmin) return { name: 'forbidden' }
        }
      }
    ]
    
    // Component-level in-component guards (Composition API)
    import { onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'
    
    // Prompt before leaving unsaved form
    onBeforeRouteLeave((to, from) => {
      if (hasUnsavedChanges.value) {
        const confirmed = window.confirm('Leave without saving?')
        if (!confirmed) return false // Cancel navigation
      }
    })
    
    // React to param changes on same route (e.g., /user/1 → /user/2)
    onBeforeRouteUpdate(async (to, from) => {
      if (to.params.id !== from.params.id) {
        user.value = await fetchUser(to.params.id)
      }
    })
    • A.Use beforeEnter in the route definition; also use onBeforeRouteLeave / onBeforeRouteUpdate in the component
    • B.Pass a guard function in the route options
    • C.Use meta.guard in the route definition
    • D.Only global guards exist; per-route guards require a plugin
    Show answer

    Answer: A. Use beforeEnter in the route definition; also use onBeforeRouteLeave / onBeforeRouteUpdate in the component

    Routes support beforeEnter for route-level guards. Components can also use onBeforeRouteLeave and onBeforeRouteUpdate composables.

    From the quiz Guards & Nested Routes

  5. 15.How do you correctly compose stores that depend on each other using setup syntax?

    javascript
    // stores/notifications.ts
    export const useNotificationStore = defineStore('notifications', () => {
      const items = ref([])
    
      function add(message: string, type = 'info') {
        items.value.push({ id: Date.now(), message, type })
      }
    
      return { items, add }
    })
    
    // stores/cart.ts — depends on notifications
    export const useCartStore = defineStore('cart', () => {
      const items = ref([])
    
      // ✅ Call useNotificationStore INSIDE the setup function
      // (not at module level) to avoid circular init issues
      const notifications = useNotificationStore()
    
      async function addItem(product) {
        items.value.push(product)
        notifications.add(`${product.name} added to cart`, 'success')
      }
    
      return { items, addItem }
    })
    
    // Circular dependency pattern — lazy resolution
    // stores/a.ts calls b.ts and b.ts calls a.ts
    // Solution: access the circular dependency inside an action, not at setup time
    export const useStoreA = defineStore('a', () => {
      const data = ref(0)
    
      function doSomething() {
        const storeB = useStoreB() // ✅ Lazy — called inside action, not during setup
        storeB.notify(data.value)
      }
    
      return { data, doSomething }
    })
    • A.Import store A inside store B's setup function at module level
    • B.Cross-store dependencies are only supported in Options syntax stores
    • C.Use inject(StoreAKey) inside store B's setup function
    • D.Call useStoreA() inside store B's setup function, and handle circular dependencies by lazy initialization
    Show answer

    Answer: D. Call useStoreA() inside store B's setup function, and handle circular dependencies by lazy initialization

    Setup stores can call other store composables inside their setup function. For circular dependencies, one store must be accessed lazily (inside an action/getter instead of at setup time).

    From the quiz Store Composition

  6. 16.Comment implémenter v-model sur un composant personnalisé en Vue 3 ?

    javascript
    <!-- Usage -->
    <MyInput v-model="username" />
    
    <!-- Equivalent to: -->
    <MyInput :modelValue="username" @update:modelValue="username = $event" />
    • A.Vue 3 utilise la prop modelValue et l'événement update:modelValue — le composant reçoit modelValue et émet update:modelValue quand la valeur change
    • B.v-model sur un composant personnalisé utilise :value et @input comme pour les inputs natifs
    • C.Il faut utiliser $emit('v-model', value) pour que v-model fonctionne
    • D.v-model ne peut pas être utilisé sur des composants personnalisés
    Show answer

    Answer: A. Vue 3 utilise la prop modelValue et l'événement update:modelValue — le composant reçoit modelValue et émet update:modelValue quand la valeur change

    Multiple v-model bindings (Vue 3.x):

    From the quiz Options API Deep-dive

  7. 17.How do you clean up side effects inside watchEffect?

    javascript
    import { watchEffect, ref } from 'vue'
    
    const searchQuery = ref('')
    
    watchEffect((onCleanup) => {
      const controller = new AbortController()
    
      // Register cleanup before starting async work
      onCleanup(() => {
        controller.abort() // Cancel previous request when query changes
      })
    
      fetch(`/api/search?q=${searchQuery.value}`, { signal: controller.signal })
        .then(r => r.json())
        .then(data => { /* update results */ })
        .catch(err => {
          if (err.name !== 'AbortError') throw err
        })
    })
    • A.Return a cleanup function from watchEffect's callback
    • B.Use the onCleanup parameter provided to the callback
    • C.Call stopEffect() before the effect re-runs
    • D.Use onBeforeUpdate to clean up before re-renders
    Show answer

    Answer: B. Use the onCleanup parameter provided to the callback

    watchEffect (and watch) provide an onCleanup function as a parameter to the callback. This function registers a cleanup callback that runs before the effect re-executes or when the watcher is stopped.

    From the quiz watch & Composables

  8. 18.How do you access server-only vs public environment variables in Nuxt?

    javascript
    // nuxt.config.ts
    export default defineNuxtConfig({
      runtimeConfig: {
        // SERVER-ONLY — never sent to client
        databaseUrl: '',          // Set via NUXT_DATABASE_URL env var
        jwtSecret: '',            // Set via NUXT_JWT_SECRET
        stripeSecretKey: '',      // Set via NUXT_STRIPE_SECRET_KEY
    
        // PUBLIC — available on both server and client
        public: {
          apiBase: '/api',        // Set via NUXT_PUBLIC_API_BASE
          appName: 'My App',      // Set via NUXT_PUBLIC_APP_NAME
          gtmId: ''               // Set via NUXT_PUBLIC_GTM_ID
        }
      }
    })
    • A.All environment variables are accessible via process.env in both server and client code
    • B.Use appConfig for all environment variables — it works in both environments
    • C.Use runtimeConfig — server-only vars in the root, public vars in runtimeConfig.public; access via useRuntimeConfig() composable
    • D.Client-side env vars must be prefixed with VITE_; server-side vars are exposed via nuxt.config.ts
    Show answer

    Answer: C. Use runtimeConfig — server-only vars in the root, public vars in runtimeConfig.public; access via useRuntimeConfig() composable

    Nuxt's runtimeConfig provides environment-specific access control:

    From the quiz SSR & Data Fetching

  9. 19.What does <KeepAlive> do and what are the associated lifecycle hooks?

    javascript
    <template>
      <nav>
        <button @click="activeTab = 'profile'">Profile</button>
        <button @click="activeTab = 'settings'">Settings</button>
        <button @click="activeTab = 'billing'">Billing</button>
      </nav>
    
      <!-- Components are cached, not destroyed on tab switch -->
      <KeepAlive :include="['ProfileTab', 'SettingsTab']" :max="5">
        <component :is="activeComponent" />
      </KeepAlive>
    </template>
    • A.It prevents a component from being garbage collected when it unmounts
    • B.It automatically persists component state to localStorage
    • C.It caches component instances so they aren't destroyed when dynamically switched, and provides onActivated/onDeactivated lifecycle hooks
    • D.It makes components render faster by precompiling their templates
    Show answer

    Answer: C. It caches component instances so they aren't destroyed when dynamically switched, and provides onActivated/onDeactivated lifecycle hooks

    <KeepAlive> wraps dynamic components to cache their state between switches instead of unmounting them. include/exclude accept component name strings, regex, or arrays. max limits cache size (LRU eviction).

    From the quiz shallowRef & markRaw

  10. 20.What is the purpose of the meta field in route definitions?

    javascript
    const routes = [
      {
        path: '/dashboard',
        component: Dashboard,
        meta: {
          requiresAuth: true,
          title: 'Dashboard',
          roles: ['admin', 'manager'],
          transition: 'slide'
        }
      },
      {
        path: '/profile',
        component: Profile,
        meta: { requiresAuth: true, title: 'Profile' }
      },
      {
        path: '/login',
        component: Login,
        meta: { requiresGuest: true, title: 'Sign In' }
      }
    ]
    
    // Using meta in global guard
    router.beforeEach((to) => {
      if (to.meta.requiresAuth && !isAuthenticated()) {
        return { name: 'login', query: { redirect: to.fullPath } }
      }
      if (to.meta.requiresGuest && isAuthenticated()) {
        return { name: 'dashboard' }
      }
    })
    
    // Accessing meta in component
    const route = useRoute()
    useHead({ title: route.meta.title }) // With @vueuse/head or similar
    • A.It stores SEO metadata for server-side rendering
    • B.It is required for named routes to work correctly
    • C.It provides a way to attach arbitrary custom data to routes, accessible in guards and components
    • D.It is only available in the beforeEach global guard
    Show answer

    Answer: C. It provides a way to attach arbitrary custom data to routes, accessible in guards and components

    meta is a free-form object that lets you attach custom data to routes — commonly used for auth requirements, page titles, breadcrumbs, or transition names.

    From the quiz Guards & Nested Routes

Senior level Vue questions

  1. 21.What is effectScope used for in Vue 3?

    javascript
    import { effectScope, ref, watch, computed } from 'vue'
    
    const scope = effectScope()
    
    scope.run(() => {
      const count = ref(0)
      const double = computed(() => count.value * 2)
      watch(count, (val) => console.log('count:', val))
      // All effects here are tracked by the scope
    })
    
    // Dispose all effects at once
    scope.stop()
    
    // Detached scope — not bound to current component's lifecycle
    const pluginScope = effectScope(true) // true = detached from parent scope
    
    pluginScope.run(() => {
      // Safe to create reactive effects here in a plugin
      // Won't be accidentally stopped when a component unmounts
    })
    
    // Clean up when Vue app is unmounted
    app.onUnmount(() => pluginScope.stop())
    • A.Limiting the scope of reactive effects to a specific component subtree
    • B.Grouping reactive effects so they can all be stopped together with a single call
    • C.Creating isolated reactive contexts for unit testing
    • D.Both B and C — effectScope is useful for both plugin architecture and testing
    Show answer

    Answer: D. Both B and C — effectScope is useful for both plugin architecture and testing

    effectScope() creates a scope that captures all reactive effects (watchers, computeds) created within it, allowing them to be disposed together with a single scope.stop() call.

    From the quiz Advanced Composables

  2. 22.How do you implement internationalization in Nuxt 3 with @nuxtjs/i18n?

    javascript
    // nuxt.config.ts
    export default defineNuxtConfig({
      modules: ['@nuxtjs/i18n'],
    
      i18n: {
        locales: [
          { code: 'en', language: 'en-US', file: 'en.json', name: 'English' },
          { code: 'fr', language: 'fr-FR', file: 'fr.json', name: 'Français' }
        ],
        defaultLocale: 'en',
        langDir: 'locales/',
    
        // Strategy: 'prefix' = /fr/about, 'prefix_except_default' = /about (en), /fr/about
        strategy: 'prefix_except_default',
    
        // Browser locale detection
        detectBrowserLanguage: {
          useCookie: true,
          cookieKey: 'i18n_redirected',
          redirectOn: 'root'
        }
      }
    })
    • A.Manually prefix all routes with locale codes and detect language via headers
    • B.Configure translations in nuxt.config.ts under the translations key
    • C.Use Vue I18n directly without any Nuxt-specific module
    • D.Use the @nuxtjs/i18n module which auto-generates locale-prefixed routes, provides useI18n() composable, and handles SSR locale detection
    Show answer

    Answer: D. Use the @nuxtjs/i18n module which auto-generates locale-prefixed routes, provides useI18n() composable, and handles SSR locale detection

    @nuxtjs/i18n integrates Vue I18n with Nuxt's routing and SSR:

    From the quiz Nitro & Modules

  3. 23.What is "partial hydration" and how does it improve SSR performance?

    javascript
    <!-- Nuxt 3 approach: lazy hydration -->
    <script setup>
    import { defineAsyncComponent } from 'vue'
    
    // Component hydrated only when it becomes visible
    const LazyChart = defineAsyncComponent({
      loader: () => import('./Chart.vue'),
      hydrate: hydrateOnVisible() // From vue3-lazy-hydration or Nuxt
    })
    </script>
    
    <template>
      <!-- Static HTML — never hydrated (pure server HTML) -->
      <header>
        <Logo />
        <StaticNav />
      </header>
    
      <!-- Interactive island — hydrated immediately -->
      <SearchBar /> <!-- Users interact with this immediately -->
    
      <!-- Lazy island — hydrated when scrolled into view -->
      <LazyChart :data="chartData" />
    
      <!-- Never hydrated — no JavaScript needed -->
      <ArticleContent :content="htmlContent" />
    </template>
    • A.Hydrating only the components that appear above the fold
    • B.Only hydrating interactive components that need JavaScript behavior, leaving static components as plain HTML
    • C.Splitting the hydration process across multiple frames using requestIdleCallback
    • D.Using JSON.parse instead of eval for state hydration
    Show answer

    Answer: B. Only hydrating interactive components that need JavaScript behavior, leaving static components as plain HTML

    Partial hydration (also called islands architecture) reduces JavaScript execution by hydrating only interactive components: In Nuxt 3, use <LazyHydrate> or #lazy modifiers:

    From the quiz Bundle Optimization

  4. 24.When do you need to use router.isReady()?

    javascript
    // SSR — server entry file
    import { createApp } from './app'
    
    export async function render(url) {
      const { app, router } = createApp()
    
      router.push(url)
      await router.isReady() // Wait for initial navigation + all guards to complete
    
      // Now render — route is correctly resolved
      const html = await renderToString(app)
      return { html }
    }
    
    // Testing
    import { mount } from '@vue/test-utils'
    import { createRouter, createWebHistory } from 'vue-router'
    
    test('dashboard redirects unauthenticated users', async () => {
      const router = createRouter({ history: createWebHistory(), routes })
      const wrapper = mount(App, { global: { plugins: [router] } })
    
      router.push('/dashboard')
      await router.isReady() // Wait for navigation guards to run
    
      expect(router.currentRoute.value.name).toBe('login')
    })
    • A.In SSR to wait for the initial navigation before rendering, or in tests to ensure guards have completed before assertions
    • B.Before every navigation to ensure the router is in a valid state
    • C.Only when using lazy-loaded routes to ensure components are downloaded
    • D.router.isReady() is deprecated — use router.currentRoute.value instead
    Show answer

    Answer: A. In SSR to wait for the initial navigation before rendering, or in tests to ensure guards have completed before assertions

    router.isReady() returns a Promise that resolves when the router has completed its initial navigation (including all navigation guards). Critical in SSR and testing contexts.

    From the quiz Lazy Loading & Transitions

  5. 25.What is the recommended way to structure stores for a large application?

    javascript
    // Domain stores — single source of truth per entity
    // stores/users.ts
    export const useUsersStore = defineStore('users', () => {
      const users = ref<Map<string, User>>(new Map())
    
      function getUser(id: string) { return users.value.get(id) }
      function setUser(user: User) { users.value.set(user.id, user) }
    
      return { users, getUser, setUser }
    })
    
    // stores/posts.ts
    export const usePostsStore = defineStore('posts', () => {
      const posts = ref<Post[]>([])
      return { posts }
    })
    
    // Feature composable — orchestrates multiple stores
    // composables/useBlogFeed.ts
    export function useBlogFeed() {
      const postsStore = usePostsStore()
      const usersStore = useUsersStore()
      const { query } = useRoute()
    
      const feedPosts = computed(() =>
        postsStore.posts
          .filter(p => !query.userId || p.authorId === query.userId)
          .map(p => ({
            ...p,
            author: usersStore.getUser(p.authorId)
          }))
      )
    
      async function loadFeed() {
        const posts = await api.getPosts()
        // Extract and cache authors without duplicating state
        const authorIds = [...new Set(posts.map(p => p.authorId))]
        const authors = await api.getUsersBatch(authorIds)
        authors.forEach(a => usersStore.setUser(a))
        postsStore.posts = posts
      }
    
      return { feedPosts, loadFeed }
    }
    • A.One large global store for all application state (like a single Vuex store)
    • B.Two stores maximum: one for server state, one for UI state
    • C.One store per component to keep state co-located with components
    • D.Domain-based stores (one per entity: user, product, cart) with feature-level composition in composables
    Show answer

    Answer: D. Domain-based stores (one per entity: user, product, cart) with feature-level composition in composables

    Domain stores manage entity state; composables orchestrate cross-store logic for specific features.

    From the quiz Plugins & SSR

  6. 26.Comment charger un composant de façon asynchrone et afficher un état de chargement ?

    javascript
    <script setup>
    import { defineAsyncComponent } from 'vue'
    
    const HeavyChart = defineAsyncComponent(() =>
      import('./HeavyChart.vue')
    )
    </script>
    
    <template>
      <Suspense>
        <template #default>
          <HeavyChart />
        </template>
        <template #fallback>
          <div>Loading chart...</div>
        </template>
      </Suspense>
    </template>
    • A.defineAsyncComponent télécharge le composant à l'avance dès le chargement de la page
    • B.Il faut utiliser un try/catch dans le parent pour gérer les erreurs de chargement
    • C.<Suspense> ne fonctionne qu'avec les Server Components — pas avec les imports dynamiques client
    • D.defineAsyncComponent crée un composant chargé lazily via code splitting — <Suspense> affiche le #fallback pendant le chargement et bascule sur #default une fois résolu
    Show answer

    Answer: D. defineAsyncComponent crée un composant chargé lazily via code splitting — <Suspense> affiche le #fallback pendant le chargement et bascule sur #default une fois résolu

    Suspense with async setup: Error handling:

    From the quiz Advanced Patterns

  7. 27.What is the correct pattern for a data-fetching composable that properly handles component unmounting and rapid re-calls?

    javascript
    import { ref, watchEffect, toValue } from 'vue'
    
    export function useFetch(urlOrGetter) {
      const data = ref(null)
      const error = ref(null)
      const loading = ref(false)
    
      watchEffect(async (onCleanup) => {
        const url = toValue(urlOrGetter)
        if (!url) return
    
        const controller = new AbortController()
        // Register cleanup BEFORE the first await
        onCleanup(() => controller.abort())
    
        loading.value = true
        error.value = null
    
        try {
          const response = await fetch(url, { signal: controller.signal })
          if (!response.ok) throw new Error(`HTTP ${response.status}`)
          data.value = await response.json()
        } catch (err) {
          if (err.name !== 'AbortError') error.value = err
        } finally {
          loading.value = false
        }
      })
    
      return { data, error, loading }
    }
    • A.Return a cancel function and rely on the consumer to call it in onUnmounted
    • B.Use onUnmounted inside the composable to abort the request
    • C.Use watchEffect with onCleanup to abort the request when dependencies change or the component unmounts
    • D.Fetch in onMounted without cleanup — browsers automatically cancel orphaned requests
    Show answer

    Answer: C. Use watchEffect with onCleanup to abort the request when dependencies change or the component unmounts

    Combining watchEffect with onCleanup provides automatic cancellation of ongoing requests when dependencies change or the component unmounts.

    From the quiz Advanced Composables

  8. 28.How does @nuxt/image optimize images in Nuxt 3?

    javascript
    // nuxt.config.ts
    export default defineNuxtConfig({
      modules: ['@nuxt/image'],
    
      image: {
        // Providers — where images come from
        domains: ['images.unsplash.com', 'cdn.myapp.com'],
    
        // Default provider (local by default)
        provider: 'ipx', // Built-in: ipx, cloudinary, imgix, bunny, etc.
    
        // Provider-specific config
        cloudinary: {
          baseURL: 'https://res.cloudinary.com/my-account/image/upload/'
        },
    
        // Responsive image sizes for srcset
        screens: {
          xs: 320, sm: 640, md: 768, lg: 1024, xl: 1280, xxl: 1536
        }
      }
    })
    • A.It automatically converts all images to WebP at build time
    • B.It compresses images in the public/ directory at build time using Sharp
    • C.It provides a <NuxtImg> component that generates optimized, resized, format-converted images on-demand via a server-side image transformation API
    • D.It replaces all <img> tags with lazy-loading versions automatically via DOM manipulation
    Show answer

    Answer: C. It provides a <NuxtImg> component that generates optimized, resized, format-converted images on-demand via a server-side image transformation API

    @nuxt/image provides on-demand image optimization with multiple provider backends:

    From the quiz Nitro & Modules

  9. 29.How do you diagnose memory leaks in a Vue application?

    javascript
    // Step 1: Chrome DevTools > Memory tab > Take snapshot (baseline)
    // Step 2: Navigate/mount/unmount the suspected component multiple times
    // Step 3: Take another snapshot
    // Step 4: Select "Comparison" view — see what objects were retained
    
    // Common Vue leak patterns to look for in heap:
    // - 'VNode' objects growing = components not unmounting
    // - 'Array' objects growing = event listeners accumulating
    // - 'Function' objects growing = closures retaining references
    
    // Programmatic leak detection:
    const weakRefs = []
    
    // After component unmounts, the weakRef should be garbage collected
    function trackForLeaks(componentInstance) {
      if (typeof WeakRef !== 'undefined') {
        weakRefs.push(new WeakRef(componentInstance))
      }
    }
    
    // Later: check if refs are collected
    async function checkForLeaks() {
      gc() // Force GC (requires --js-flags=--expose-gc in Chrome)
      const survived = weakRefs.filter(ref => ref.deref() !== undefined)
      console.log(`${survived.length} components still alive after unmount`)
    }
    
    // Common leak patterns:
    // 1. Event listeners on window/document not removed in onUnmounted
    // 2. setInterval not cleared in onUnmounted
    // 3. WebSocket not closed in onUnmounted
    // 4. Observer (IntersectionObserver, ResizeObserver) not disconnected
    // 5. Pinia store subscription not stopped
    • A.Count the number of components — too many components always indicates a leak
    • B.Use Chrome DevTools Memory tab: take heap snapshots before and after navigation, compare retained objects to identify leaks
    • C.Watch the number of events in the Vue DevTools — a growing count indicates leaks
    • D.Memory leaks are not possible in Vue 3 due to WeakRef-based reactivity
    Show answer

    Answer: B. Use Chrome DevTools Memory tab: take heap snapshots before and after navigation, compare retained objects to identify leaks

    Memory leak diagnosis requires comparing heap states before and after operations:

    From the quiz Bundle Optimization

  10. 30.What are named views in Vue Router and when do you use them?

    javascript
    // Route with named views
    const routes = [
      {
        path: '/',
        components: { // Note: plural
          default: MainContent,
          sidebar: Sidebar,
          header: Header
        }
      },
      {
        path: '/dashboard',
        components: {
          default: DashboardMain,
          sidebar: DashboardSidebar,
          header: DashboardHeader
        }
      }
    ]
    • A.Named routes with human-readable path names instead of patterns
    • B.Multiple <router-view> outlets in the same layout, each rendering a different component for the same route
    • C.Dynamic component names that can be swapped at runtime
    • D.Route aliases — accessing the same component via different names
    Show answer

    Answer: B. Multiple <router-view> outlets in the same layout, each rendering a different component for the same route

    Named views allow rendering multiple components simultaneously for a single route — useful for complex layouts with independent sections.

    From the quiz Lazy Loading & Transitions

Practise 650+ more Vue questions

Timed quizzes with instant feedback, the questions you miss coming back until they stick, coding challenges and AI mock interviews — free to start.