Vue-in-Nuxt component authoring
Patterns for writing Vue 3 <script setup> components inside a Nuxt 4 app. This
is the frontend authoring slice — the data layer (useFetch/$fetch, SSR
storage, hydration, definePageMeta/auth) lives in the nuxt-nitro-api skill;
Volt/PrimeVue styling and dark mode live in volt-primevue. This skill
cross-links to those rather than restating them.
When to Use This Skill
- Authoring or reviewing a
.vuecomponent in a Nuxt 4 project - Deciding how a component is named / auto-imported
- Typing props & emits, defaulting props, building a generic component
- Wiring
v-modelon a component - Designing a component's content API — props vs slots, named/scoped slots
- Authoring a composable — argument shape, what to return, cleanup
- Structuring a page — thin pages, components own the data + logic
- Formatting display values (currency/date/number) consistently
- Anything reactivity-shaped:
computedvswatch, prop→state sync, DOM measurement - You see
watchand want to know if it should be something else - About to hand-roll a
watch+ lifecycle teardown for a browser API (storage, timers, DOM, listeners, URL) — VueUse likely wraps it
Reference Files
- auto-imports.md — what auto-imports (components with dir-prefix names, composables, utils, Vue/Nuxt APIs) and what does NOT (third-party, types, test files)
- component-authoring.md — type-only
defineProps/defineEmits,withDefaults, the Boolean-prop trap, factory defaults, generic components,defineExpose, what to extract into a shared component - v-model.md —
defineModelvs the props+emit+computed proxy, named models, paired fields - slots.md — slots vs props for markup, named/scoped slots,
defineSlots/useSlots, avoiding empty wrappers, forwarding, slot transitions - composables.md —
MaybeRefOrGetter/toValueargument contract, return refs notreactive(), thin pure-core shell,onScopeDispose/effectScopecleanup - reactivity.md —
refoverreactive,useTemplateRef, pure computeds, mutate-don't-reassign, DOM-measure +ResizeObserver,shallowRef, watch-getter prop sync,:keyremount, listener cleanup - watch.md —
watchis the escape hatch, not the default: when it's right, and the four smell shapes (with refactors) found auditing 159 real watchers - vueuse.md — reach for a VueUse composable before hand-rolling a
watch+ lifecycle teardown for an external-world effect (DOM, timers, storage, URL, listeners); the@vueuse/nuxtsetup, the watch-sugar functions, and what to keep on Nuxt's own APIs - template-idioms.md — duplicate-
@keyupTS error,:deep()/:slotted()/:global(), click-outside marker class,NuxtLink/thinapp.vue,useHead,v-bindshorthand,useId,<Teleport>/<KeepAlive>,v-memo/v-once, file-input reset - page-structure.md — keep pages thin: route-param parsing + layout in the page, data/logic/forms in components
- formatters.md — never inline a currency/date/number formatter; centralize in
useFormatters, prefer Intl/date-fns
Testing note: when a Vue/Nuxt refactor changes component behavior, route/query
state, or a composable with lifecycle hooks, use the Nuxt frontend testing
guidance in nitro-testing's frontend-testing.md
instead of testing those pieces as plain Vue functions.
Core Principles
- Lean on auto-imports.
app/components,app/composables,app/utils, and the Vue/Nuxt APIs all auto-import. Add an explicitimportonly for third-party symbols and TS types. A nested component's tag carries its directory as a prefix (components/customers/ProfileCard.vue→<CustomersProfileCard>). - Type props/emits, default the booleans. Use the type-only macros (
defineProps<{...}>(),defineEmits<{...}>()). A barebooleanprop coerces tofalsewhen absent (notundefined), so any "defaults-on" flag MUST be defaulted — via reactive destructure ({ flag = true } = defineProps<…>(), the 3.5 default, no factory needed for arrays/objects) orwithDefaults(factory required for non-primitives). computedfor derivation,watchfor escaping the graph. If a watcher body just assigns one reactive value from others, it's acomputed. Need to write a value back? Acomputedcan have a setter — reach for a writablecomputedordefineModelbefore a sync watcher. Keep computed getters pure (no fetch, no mutation, no DOM).- Tie effects to lifecycle. DOM measurement, listeners, observers, and timers go in
onMountedand are torn down inonUnmounted. A computed reading live DOM geometry needs an explicit re-measure signal (DOM size isn't reactive). Before hand-rolling that effect-plus-teardown, check whether a VueUse composable already wraps it (vueuse.md) — they bundle the cleanup. - Call composables at the top of
<script setup>— never inside a callback or a template expression (both lose Nuxt's request scope). Derive display state withcomputed, guarding for possibly-null data. - Defer to the right skill. Fetch/SSR/auth/middleware →
nuxt-nitro-api. Volt components,pt:styling, color tokens, dark mode →volt-primevue. Don't duplicate them here.
Contributing Back
If you hit a Vue-in-Nuxt authoring gotcha this skill doesn't cover (or one it gets
wrong), upstream it — run /contribute-skill.