All skills
hyf0 avatar

/vue-debug-guides

@a5fc891 official
by hyf0hyf0/vue-skills2.9k stars
167

Vue 3 debugging and error handling for runtime errors, warnings, async failures, and SSR/hydration issues. Use when diagnosing or fixing Vue issues.

Use this Skill: https://skilld.dev/gh/hyf0/vue-skills/vue-debug-guides

This session only. Nothing lands on disk.

referencecomposition-api-script-setup-async-context.md

≈1.3k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Top-Level await in script setup Preserves Component Context

Impact: HIGH - In <script setup>, top-level await statements preserve component context (allowing lifecycle hooks and watchers after await), but this is a special case. Nested async functions or callbacks lose context, causing lifecycle hooks to silently fail.

Vue's compiler automatically injects context restoration after each top-level await in <script setup>. This doesn't apply to setup() function or nested async contexts.

Task Checklist

  • Understand that top-level await in <script setup> is specially handled
  • Never register lifecycle hooks in nested async functions
  • Use <Suspense> when using async <script setup> components
  • In regular setup(), never use await before lifecycle hook registration
  • Register hooks synchronously, then do async work inside them

Top-Level await Works (script setup only):

<script setup>
import { ref, onMounted, watch } from 'vue'

// This is TOP-LEVEL await - Vue compiler preserves context
const config = await fetchConfig()  // OK!

// These hooks work because Vue restored context
onMounted(() => {
  console.log('This will run!')  // Works
})

watch(someRef, () => {
  console.log('This will track!')  // Works
})

// Another top-level await - still OK
const data = await fetchData(config.apiUrl)  // OK!

// Still works after multiple awaits
onMounted(() => {
  console.log('This also runs!')  // Works
})
</script>

<!-- IMPORTANT: Parent must use Suspense -->
<template>
  <Suspense>
    <AsyncComponent />
  </Suspense>
</template>

Nested Async Breaks Context:

<script setup>
import { ref, onMounted, watch } from 'vue'

// WRONG: Nested async function - context lost after await
async function initializeData() {
  const config = await fetchConfig()

  // BUG: This hook will NOT be registered!
  // We're no longer in the synchronous setup context
  onMounted(() => {
    console.log('This will NEVER run!')  // Silent failure
  })

  // BUG: This watcher won't auto-dispose on unmount
  watch(someRef, () => {
    console.log('Memory leak - not cleaned up!')
  })
}

// Calling the async function
initializeData()  // Hooks inside won't work!

// WRONG: Callbacks also lose context
setTimeout(async () => {
  await delay(100)
  onMounted(() => {
    console.log('Never runs!')  // Silent failure
  })
}, 0)
</script>

Correct Patterns:

<script setup>
import { ref, onMounted, watch } from 'vue'

const data = ref(null)
const config = ref(null)

// CORRECT: Register hooks synchronously FIRST
onMounted(async () => {
  // Then do async work INSIDE the hook
  config.value = await fetchConfig()
  data.value = await fetchData(config.value.apiUrl)
})

// CORRECT: Watchers registered synchronously
watch(config, async (newConfig) => {
  if (newConfig) {
    data.value = await fetchData(newConfig.apiUrl)
  }
})

// Or use top-level await for initial data
const initialConfig = await fetchConfig()  // OK - top level
config.value = initialConfig

onMounted(() => {
  console.log('Works!')  // Context preserved by compiler
})
</script>

setup() Function (Not script setup):

// In regular setup(), await ALWAYS breaks context
export default {
  async setup() {
    const data = ref(null)

    // WRONG: Hooks after await won't register
    const config = await fetchConfig()

    onMounted(() => {
      console.log('Never runs!')  // Silent failure!
    })

    return { data }
  }
}

// CORRECT: Register hooks before any await
export default {
  async setup() {
    const data = ref(null)

    // Register hooks FIRST (synchronous)
    onMounted(async () => {
      const config = await fetchConfig()
      data.value = await fetchData(config)
    })

    // Now you can await if needed
    // But hooks must be registered before this point

    return { data }
  }
}

Why This Happens

// Vue tracks the "current component instance" during setup
// This is like a global variable that gets set and cleared

// During synchronous setup:
function setup() {
  currentInstance = this  // Vue sets this

  onMounted(cb)  // Uses currentInstance to register

  // After await, JavaScript resumes in a microtask
  await something()

  // currentInstance is now null or different!
  onMounted(cb)  // Can't find the instance - silently fails
}

// <script setup> compiler adds restoration:
// After each await, it injects: setCurrentInstance(savedInstance)

Suspense Requirement

<!-- When using async script setup, parent needs Suspense -->
<template>
  <Suspense>
    <!-- Async component with top-level await -->
    <AsyncChild />

    <!-- Optional: Loading state -->
    <template #fallback>
      <LoadingSpinner />
    </template>
  </Suspense>
</template>

Reference

Source: SKILL.md on GitHub

No alerts17d5 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    This skill is a comprehensive collection of educational debugging guides and best practices for Vue 3 developers. It contains no executable code or malicious instructions and focuses solely on diagnosing common runtime errors and performance issues.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • Runlayer7mo

    140 files scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at a5fc891. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 8 months ago

README badge

README badge for hyf0/vue-skills/vue-debug-guides

Guides for diagnosing and fixing Vue 3 runtime errors, reactivity bugs, async failures, and hydration issues across components, templates, watchers, forms, and lifecycle hooks. Covers specific failure modes like stale watchers, ref unwrapping edge cases, v-model sync problems, and SSR mismatches with concrete reference links for each issue.

Generated from the current SKILL.md.

Does this skill cover Vue 2?
No. This skill is specific to Vue 3 debugging and error handling.
What should I use for Vue development best practices instead of debugging?
The skill references a separate `vue-best-practices` skill for development best practices and common gotchas.
Does this cover SSR and hydration issues?
Yes. The skill includes guides for diagnosing and fixing SSR rendering differences and hydration bugs.
Can this help with async watcher and watchEffect problems?
Yes. The skill covers async operation stale data, dependency tracking after await, and proper flush timing for watchers.
Does this address v-model and form-related issues?
Yes. The skill includes guides for v-model initial values, textarea interpolation, IME composition, and custom checkbox form submission.

Generated from the current SKILL.md. These answers refresh after source changes.