Cursor plugin for Vue 3.5 + Nuxt 4 + TypeScript. Pinned to vue ^3.5.34 and nuxt ^4.4.5. Teaches the Vue 3.5 trio that LLMs trained pre-September 2024 do not know (useTemplateRef('name'), useId(), reactive props destructuring with defaults), the Nuxt 4 app/ srcDir flatten (with server/, shared/, public/, modules/ at the root), the useAsyncData / useFetch shallowRef + singleton-by-key behaviour, Pinia 3 setup stores, VueUse 14 foundational composables, and Vitest 4 + Vue Test Utils 2 + @nuxt/test-utils. Catches 38 LLM regressions with BAD / CORRECT TypeScript + Vue pairs.
LLMs trained on Vue 3.0 / 3.2 / 3.3 / 3.4 and Nuxt 2 / 3.x emit code that does not match a fresh Vue 3.5 + Nuxt 4 install. They write:
ref<HTMLInputElement | null>(null)template refs (Vue 3.5 introduceduseTemplateRef('name'))withDefaults(defineProps<{}>(), {...})wrapping (Vue 3.5 made it redundant - destructure with defaults is the documented form)toRefs(props)boilerplate afterdefineProps(Vue 3.5 destructure is reactive)Options APIblocks (data()/methods/mounted) in new componentsVue.use(plugin)/new Vue({})(Vue 2 globals; Vue 3 has none){{ x | currency }}template filters (removed in Vue 3).syncmodifier (usev-model:propName+defineModel)created()/beforeDestroy()lifecycle hooks (renamed / removed)Vuex createStorefor new projects (Pinia is the official state library)- Pinia options-style stores (
defineStore('id', { state, getters, actions })instead of the setup form) - MODULE-SCOPE
ref()in a Nuxt composable - cross-request SSR leak; CRITICAL bug - Manual
import { ref, computed } from 'vue'inside a Nuxt SFC (auto-imported) pages/,components/,composables/at the project root in a Nuxt 4 project (must be underapp/)future: { compatibilityVersion: 3 }in a Nuxt 4nuxt.config.ts(flag was REMOVED in Nuxt 4)store/directory (Vuex / Nuxt 2 naming; should bestores/and in Nuxt 4app/stores/)readBody(event)in a GET handler (GET has no body in HTTP)server/api/posts.tswithout method suffix (.get.ts/.post.ts/ etc.)throw new Error('...')from a server route (usecreateError({ statusCode, statusMessage, data }))queryContent('/blog')in a@nuxt/contentv3 project (usequeryCollection('posts').path(...).first())- Mutating
data.value.someFieldon auseAsyncDatapayload (Nuxt 4 default isshallowRef) - Two
useFetch('/api/x')calls without explicitkey:(Nuxt 4 makes them singleton-by-key) - Watchers with
await fetch(...)but noonWatcherCleanup()to abort (in-flight fetch leak) - Hand-rolled
addEventListener+onUnmountedcleanup instead of VueUseuseEventListener - Hand-rolled click-outside / debounce instead of VueUse
onClickOutside/useDebounceFn - Static
<Teleport to="#modal">when the target may not exist yet (Vue 3.5 added<Teleport defer>) - Eager imports of below-the-fold heavy components (Vue 3.5 added lazy hydration via
defineAsyncComponent({ hydrate: ... })) vitest.config.tswithout explicitpool: 'forks'(Vitest 4 default flipped from v3's'threads')- Cypress component tests as the recommended path (Vitest + Vue Test Utils +
@nuxt/test-utilsis documented)
A handful of Vue and Nuxt rules already live on cursor.directory: vue.mdc, vue3-composition-api-cursorrules-prompt-file.mdc, vue-3-nuxt-3-development-cursorrules-prompt-file.mdc, vue-pinia-cursorrules-prompt-file.mdc, vue-claude-stack.mdc, and the largest single file sanjeed5/nuxt.mdc (387 lines). They are shallow and have three structural problems this plugin fixes:
-
They pin nothing or pin pre-3.5 / pre-4. Across all of these, zero mentions of
useTemplateRef,useId, reactive props destructure with defaults, Nuxt 4, theapp/srcDir layout, theshallowRefdefault onuseAsyncData, the singleton-by-key behaviour, oronWatcherCleanup(). Vue 3.5 (Sep 2024) and Nuxt 4 (Jul 2025) post-date all of them. This plugin pinsvue ^3.5.34andnuxt ^4.4.5explicitly. -
The largest single file (
sanjeed5/nuxt.mdc, 387 lines) still teaches thestore/directory - deprecated forstores/even in Nuxt 3, andapp/stores/in Nuxt 4. Thevue-pinia-cursorrules-prompt-file.mdcteaches Pinia options-style stores rather than the documented setup-store shape. -
They ship flat
.cursorrulestext without globs, fixtures, skills, or an agent. This plugin ships:- 10 MDC rules with proper
globsso the server-route check fires onserver/api/**, the Pinia check onapp/stores/**, the VueUse check onapp/**/*.vue, etc. - 38 documented anti-patterns with BAD / CORRECT TypeScript + Vue pairs (the existing rules have zero)
- 5 skills:
/vue-new-component,/nuxt-new-server-route,/nuxt-new-pinia-store,/vue-nuxt-migrate-to-3-5-and-4,/vue-nuxt-validate - 1 reviewer agent with severity grouping (CRITICAL / ERROR / WARN / NIT)
- 2 fixture projects:
correct-sample(gold-standard Vue 3.5.34 + Nuxt 4.4.5 + Pinia 3 + Nuxt UI 4 + Zod) andanti-pattern-sample(16 distinct numbered violations across 6 files, pinned tovue ^3.3.0+nuxt ^3.0.0+vuex ^4.1.0to demonstrate the v3.3 / v3 / Vuex hangover)
- 10 MDC rules with proper
Copy the rules, skills, and agent into your project's Cursor configuration. Back up your existing files first; cp -r will overwrite same-named rules.
git clone https://github.com/RoninForge/roninforge-vue-nuxt.git
# Use -n to avoid clobbering an existing customised rule of the same name.
cp -rn roninforge-vue-nuxt/rules/* your-project/.cursor/rules/
cp -rn roninforge-vue-nuxt/skills/* your-project/.cursor/skills/
cp -rn roninforge-vue-nuxt/agents/* your-project/.cursor/agents/Or vendor the whole repo as a git submodule under your-project/.cursor/plugins/. Refer to the Cursor plugin docs for the current global-install path on your Cursor version.
| Rule | Scope (globs) | What it does |
|---|---|---|
vue-nuxt-anti-patterns |
**/*.vue,**/*.ts,**/*.js,nuxt.config.ts |
38 LLM regressions with BAD / CORRECT pairs. Each entry annotates which Vue / Nuxt version dropped or renamed the BAD form |
vue-3-5-core |
**/*.vue,**/*.ts,**/*.js |
Vue 3.5 trio: useTemplateRef, useId, reactive props destructure. Plus onWatcherCleanup, <Teleport defer>, lazy hydration |
vue-script-setup |
**/*.vue |
<script setup lang="ts"> canonical: defineProps / defineEmits (tuple) / defineModel / defineSlots / defineOptions / defineExpose, generic components, no setup() nesting |
nuxt-4-core |
nuxt.config.ts,nuxt.config.js,app/**/*,server/**/*,shared/**/*,modules/**/* |
app/ srcDir for client code, server/ shared/ public/ modules/ at root, no compatibilityVersion, split TypeScript projects, auto-imports |
nuxt-data-fetching |
app/**/*.vue,app/**/*.ts,server/**/*.ts,composables/**/*.ts,app/composables/**/*.ts |
useFetch / useAsyncData Nuxt 4 shallowRef default, singleton-by-key, reactive keys, transform / pick, error handling, lazy / useLazyFetch |
nuxt-state-management |
app/stores/**/*.ts,stores/**/*.ts,app/composables/**/*.ts,composables/**/*.ts,app/**/*.vue |
Decision tree: ref / useState / Pinia. Why module-scope ref() in a Nuxt composable is a SSR cross-request leak |
nuxt-server-routes |
server/api/**/*.ts,server/routes/**/*.ts,server/middleware/**/*.ts,server/utils/**/*.ts |
Filename method suffixes, defineEventHandler, getValidatedQuery + readValidatedBody with Zod, createError, server middleware |
pinia |
app/stores/**/*.ts,stores/**/*.ts |
Pinia 3 setup-store shape, storeToRefs, no mapState / mapGetters, acceptHMRUpdate, $patch / $reset / $subscribe, isolated unit testing |
vueuse |
app/**/*.vue,app/**/*.ts,components/**/*.vue,composables/**/*.ts |
VueUse 14 foundational composables (useEventListener, onClickOutside, useDebounceFn, useIntersectionObserver, useDark, useStorage) |
vue-nuxt-testing |
tests/**/*,test/**/*,**/*.test.ts,**/*.spec.ts,vitest.config.ts |
Vitest 4 + Vue Test Utils 2 + @nuxt/test-utils + Playwright. Vitest 4 default pool 'forks' (was 'threads' in v3) |
| Skill | Command | What it does |
|---|---|---|
| New component | /vue-new-component |
Scaffold a <script setup lang="ts"> component using useTemplateRef, useId, reactive props destructure with defaults, defineEmits tuple form, defineExpose, plus matching Vitest test |
| New server route | /nuxt-new-server-route |
Scaffold a server/api/<resource>/<segment>.<method>.ts handler with Zod-validated query / body, createError for failures, typed return value referencing ~~/shared/types, plus matching @nuxt/test-utils test |
| New Pinia store | /nuxt-new-pinia-store |
Scaffold a defineStore('name', () => { ... }) setup store with state refs + computed getters + action functions, acceptHMRUpdate block, plus a test using setActivePinia(createPinia()) |
| Migrate to 3.5 + 4 | /vue-nuxt-migrate-to-3-5-and-4 |
Stage-by-stage migration: bump pins, move srcDir to app/, drop compatibilityVersion, rename store/ to stores/, replace Vuex with Pinia setup stores, swap ref(null) for useTemplateRef, drop withDefaults / toRefs(props), add onWatcherCleanup(), add server route method suffixes, swap queryContent for queryCollection, set vitest pool: 'forks' |
| Validate | /vue-nuxt-validate |
Run vue-tsc + ESLint + Vitest in parallel and grep-audit the codebase for tracked Vue 3.5 + Nuxt 4 anti-patterns |
| Agent | What it does |
|---|---|
vue-nuxt-reviewer |
Reviews Vue 3.5 + Nuxt 4 + TypeScript code by severity. CRITICAL: module-scope ref() in Nuxt composable, readBody in GET handler, throw new Error from server route, Vuex createStore, compatibilityVersion: 3 in Nuxt 4 config, ref(null) template refs, pages/components/composables at root in Nuxt 4. ERROR: Options API in new SFC, withDefaults wrapping, toRefs(props), Pinia options-style, server route without method suffix, queryContent in v3 project, store/ directory, manual Vue imports inside Nuxt SFC. WARN: missing onWatcherCleanup, useFetch without explicit key, mutating data.value.* on shallowRef, missing useId for ARIA, hand-rolled addEventListener over useEventListener, hand-rolled click-outside / debounce, eager imports of heavy below-fold components, static <Teleport> to lazy target, Vitest pool not declared. NIT: missing defineExpose surface, missing storeToRefs around state destructure, missing acceptHMRUpdate |
tests/fixtures/correct-sample/ is a slim Vue 3.5.34 + Nuxt 4.4.5 + Pinia 3 + Nuxt UI 4 + Zod project demonstrating the gold-standard shape: app/ srcDir layout (with server/, shared/, public/ at root), <script setup lang="ts"> everywhere, useTemplateRef('name') instead of ref(null) for DOM handles, useId() for SSR-safe ARIA, reactive props destructure with defaults (no withDefaults, no toRefs(props)), onWatcherCleanup() to abort in-flight fetches inside watchers, useAsyncData with explicit keys + reactive keys for route params (respecting Nuxt 4's shallowRef default), Pinia 3 setup stores with acceptHMRUpdate, server routes with method-suffixed filenames using getValidatedQuery / readValidatedBody + Zod and createError({ statusCode, statusMessage, data }) for failures, and a shared/types.ts referenced from both client and server.
tests/fixtures/anti-pattern-sample/ is the inverse. Every file violates a numbered anti-pattern. package.json pins vue ^3.3.0 + nuxt ^3.0.0 + vuex ^4.1.0 on purpose - the v3.3 / v3 / Vuex combination is a valid peer-dep window where Vuex was the only state library and the Nuxt 3 srcDir layout was the default. Tracked violations: #1 (Options API), #3 (filter), #4 (event-bus shape), #6 (created / beforeDestroy), #7 ($set), #8 ($refs), #11 (Vuex), #13 (module-scope ref leak), #14 (manual Vue import), #17 (defineComponent in SFC), #22 (readBody in GET), #23 (no method suffix), #24 (throw new Error), #25 (root-level pages/components/composables), #26 (compatibilityVersion: 3), #27 (store/ directory). The fixture's README maps every violation to file:line.
Rules target vue ^3.5.34 on nuxt ^4.4.5 with Node 20+ (22 recommended). Most patterns work back to Vue 3.4 / Nuxt 3.12 with the deltas called out inline. Where the rule cites a version (useTemplateRef 3.5, useId 3.5, reactive props destructure 3.5, onWatcherCleanup 3.5, <Teleport defer> 3.5, lazy hydration 3.5, app/ srcDir Nuxt 4 default, shallowRef default Nuxt 4, singleton-by-key Nuxt 4, compatibilityVersion removed Nuxt 4), verify against the changelog for the version you have installed before adopting.
MIT - see LICENSE