vue-nuxt-best-practices

57 performance optimization rules for Vue3 and Nuxt4 applications. Covers SSR, data fetching, reactivity, component design, state management, and bundle optimization.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "vue-nuxt-best-practices" with this command: npx skills add king-gd/vue-nuxt-best-practices/king-gd-vue-nuxt-best-practices-vue-nuxt-best-practices

Vue3 & Nuxt4 Best Practices

You are an expert Vue3 and Nuxt4 developer. When writing, reviewing, or refactoring code, always apply these best practices.

Priority Levels

  • CRITICAL: Must follow, violations cause bugs or major performance issues
  • HIGH: Strongly recommended, significant impact on performance/maintainability
  • MEDIUM: Recommended for better code quality
  • LOW: Nice to have, advanced optimizations

1. SSR & Hydration (Critical)

Avoid Hydration Mismatch

Server and client renders must produce identical HTML.

<!-- ❌ BAD: Different values on server vs client -->
<template>
  <div>{{ Date.now() }}</div>
  <div>{{ Math.random() }}</div>
  <div>{{ window.innerWidth }}</div>
</template>

<!-- ✅ GOOD: Use ClientOnly or onMounted -->
<template>
  <ClientOnly>
    <div>{{ currentTime }}</div>
    <template #fallback>Loading...</template>
  </ClientOnly>
</template>

<script setup lang="ts">
const currentTime = ref<number | null>(null)
onMounted(() => {
  currentTime.value = Date.now()
})
</script>

Use import.meta.client for Environment Checks

// ✅ GOOD: Nuxt 3/4 recommended way
if (import.meta.client) {
  // Client-only code
  initAnalytics()
}

if (import.meta.server) {
  // Server-only code
}

// ❌ BAD: Deprecated
if (process.client) { }

Use onMounted for Browser APIs

// ❌ BAD: Crashes on server
const width = window.innerWidth

// ✅ GOOD: Safe for SSR
const width = ref(0)
onMounted(() => {
  width.value = window.innerWidth
})

// ✅ BETTER: Use VueUse
import { useWindowSize } from '@vueuse/core'
const { width } = useWindowSize()

2. Data Fetching (Critical)

Use useFetch Instead of $fetch

// ❌ BAD: Fetches twice (server + client)
const data = await $fetch('/api/users')

// ✅ GOOD: SSR-aware, deduped, cached
const { data, pending, error, refresh } = await useFetch('/api/users')

Parallelize Independent Requests

// ❌ BAD: Sequential (slow)
const { data: user } = await useFetch('/api/user')
const { data: posts } = await useFetch('/api/posts')
const { data: comments } = await useFetch('/api/comments')

// ✅ GOOD: Parallel (fast)
const [{ data: user }, { data: posts }, { data: comments }] = await Promise.all([
  useFetch('/api/user'),
  useFetch('/api/posts'),
  useFetch('/api/comments')
])

Use Lazy for Non-Critical Data

// Critical data: blocks SSR
const { data: mainContent } = await useFetch('/api/content')

// Non-critical: loads after hydration
const { data: recommendations, pending } = useLazyFetch('/api/recommend')
const { data: comments } = useFetch('/api/comments', { lazy: true })

Set Unique Keys to Avoid Duplicate Requests

const { data } = await useFetch(`/api/article/${id}`, {
  key: `article-${id}`
})

3. Reactivity (High)

Use shallowRef for Large Objects

// ❌ BAD: Deep reactivity overhead for 10000 items
const tableData = ref<User[]>([])

// ✅ GOOD: Only .value is reactive
const tableData = shallowRef<User[]>([])

// Update by replacing entire array
tableData.value = newData

// Or mutate + trigger manually
tableData.value[0].name = 'New'
triggerRef(tableData)

Never Destructure reactive() Objects

const state = reactive({ count: 0, name: 'Vue' })

// ❌ BAD: Loses reactivity
const { count } = state
count++ // Won't trigger updates!

// ✅ GOOD: Use toRefs
const { count } = toRefs(state)
count.value++ // Works!

// ✅ BETTER: Use ref instead of reactive
const count = ref(0)

Use computed for Derived Values

<!-- ❌ BAD: Recalculates every render -->
<div>{{ items.filter(i => i.active).length }}</div>

<!-- ✅ GOOD: Cached -->
<script setup>
const activeCount = computed(() => items.value.filter(i => i.active).length)
</script>
<div>{{ activeCount }}</div>

4. Component Design (High)

Lazy Load Heavy Components

// ❌ BAD: Loaded immediately
import HeavyChart from './HeavyChart.vue'

// ✅ GOOD: Loaded on demand
const HeavyChart = defineAsyncComponent(() => import('./HeavyChart.vue'))

// ✅ NUXT: Auto lazy with Lazy prefix
<template>
  <LazyHeavyChart v-if="showChart" />
</template>

v-if vs v-show

ScenarioUse
Frequent toggle (hover, tabs)v-show
Rarely changesv-if
Initially falsev-if
Heavy componentv-if

Type Props with TypeScript

interface Props {
  user: User
  items: string[]
  loading?: boolean
  size?: 'sm' | 'md' | 'lg'
}

const props = withDefaults(defineProps<Props>(), {
  loading: false,
  size: 'md',
  items: () => []
})

5. Performance (Medium)

Use v-memo for List Optimization

<div
  v-for="item in items"
  :key="item.id"
  v-memo="[item.id, item === selected]"
>
  <ExpensiveComponent :item="item" />
</div>

Use Virtual Scrolling for Large Lists

// For 1000+ items, use virtual scrolling
import { useVirtualList } from '@vueuse/core'

const { list, containerProps, wrapperProps } = useVirtualList(items, {
  itemHeight: 40
})

Debounce/Throttle High-Frequency Events

import { useDebounceFn, useThrottleFn } from '@vueuse/core'

// Search input: debounce
const debouncedSearch = useDebounceFn(search, 300)

// Scroll handler: throttle
const throttledScroll = useThrottleFn(handleScroll, 100)

Use KeepAlive for Cached Components

<KeepAlive :include="['TabA', 'TabB']" :max="5">
  <component :is="currentTab" />
</KeepAlive>

6. State Management - Pinia (Medium)

Use storeToRefs for Destructuring

import { storeToRefs } from 'pinia'

const store = useUserStore()

// ❌ BAD: Loses reactivity
const { user, isLoggedIn } = store

// ✅ GOOD: Keeps reactivity
const { user, isLoggedIn } = storeToRefs(store)

// Actions can be destructured directly
const { login, logout } = store

Use $patch for Batch Updates

// ❌ BAD: Multiple reactive updates
store.user.name = 'New'
store.user.email = 'new@example.com'
store.updatedAt = new Date()

// ✅ GOOD: Single update
store.$patch({
  user: { ...store.user, name: 'New', email: 'new@example.com' },
  updatedAt: new Date()
})

7. Bundle Optimization (Low)

Tree-Shaking Friendly Imports

// ❌ BAD: Imports entire lodash (~70KB)
import _ from 'lodash'

// ✅ GOOD: Only imports debounce (~2KB)
import { debounce } from 'lodash-es'

Use Auto-Import for Component Libraries

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@element-plus/nuxt'],
  // Auto-imports only used components
})

Analyze Bundle Size

npx nuxi analyze

8. Nuxt Specific (Low)

Configure Route Rules for Caching

// nuxt.config.ts
export default defineNuxtConfig({
  routeRules: {
    '/': { prerender: true },
    '/blog/**': { isr: 3600 },        // Regenerate hourly
    '/api/products': { swr: 60 },     // Stale-while-revalidate
    '/admin/**': { ssr: false }       // Client-only
  }
})

Use useState for Simple Shared State

// SSR-safe shared state (simpler than Pinia)
const user = useState<User | null>('user', () => null)
const theme = useState('theme', () => 'light')

Use definePageMeta for Page Configuration

<script setup lang="ts">
definePageMeta({
  layout: 'admin',
  middleware: ['auth'],
  keepalive: true
})
</script>

Quick Reference

IssueSolution
Hydration mismatchUse <ClientOnly> or onMounted
Double fetchingUse useFetch not $fetch
Slow sequential requestsUse Promise.all()
Large list performanceUse virtual scrolling
Reactivity lostDon't destructure reactive(), use toRefs()
Store reactivity lostUse storeToRefs()
Large bundleUse dynamic import() and tree-shaking

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

nuxt-best-practices

No summary provided by upstream source.

Repository SourceNeeds Review
General

vue-best-practices

No summary provided by upstream source.

Repository SourceNeeds Review
General

nuxt-seo-best-practices

No summary provided by upstream source.

Repository SourceNeeds Review
General

test_skill

import json import tkinter as tk from tkinter import messagebox, simpledialog

Archived SourceRecently Updated