nuxt — Cursor rule: what it does, how to install
A Cursor rule that keeps the agent writing Nuxt the conventional way: folder layout, Composition API, Pinia, useFetch. Useful once the agent starts reaching for mixins and onMounted.
.cursor/rules/nuxt.mdcWhat nuxt does
The rule spells out how a Nuxt app should be laid out. Folders keep their fixed roles: components/, composables/, layouts/, middleware/, pages/, plugins/, server/, store/, utils/. Components go in PascalCase.vue, composables in usePascalCase.ts, pages and layouts in kebab-case. Auto-imports then resolve without hand-written paths.
Most of the file is bad/good pairs. Mixins give way to composables, and prop-heavy cards give way to named slots. Pinia state changes only inside actions, and stores are split per feature. Data comes through useFetch or useAsyncData rather than onMounted plus $fetch, which on SSR fires the request twice. Errors are read from error.value and rethrown with createError; routes are guarded in middleware.
On performance the rule asks for <NuxtLink> on internal navigation, routeRules for hybrid rendering, a Lazy prefix on heavy components, and <NuxtImg> from @nuxt/image. That last one needs the module installed, otherwise a template using <NuxtImg> will not build. Drop the file at .cursor/rules/nuxt.mdc; it applies to **/*.{vue,js,ts}.
What it helps with
- teach the agent Nuxt folder conventions before it writes any files
- swap mixins for composables in a legacy component
- move data loading from
onMountedtouseFetch - split one oversized Pinia store into feature stores
- set up
routeRulesfor prerendered, SWR and client-only pages
Example prompts for the agent
- Rewrite this component with
<script setup lang="ts">and a composable instead of the mixin - Replace the
$fetchcall inonMountedwithuseFetchand handleerror.value - Split the
userstore intoauthandprofileand stop mutating state directly
How to install nuxt
Manually: put the files in place
.cursor/rules/nuxt.mdcFrontmatter: description, globs, alwaysApply
Manually: put the files in place
.claude/rules/nuxt.mdThe paths frontmatter field limits the files: move globs from the source into paths
AGENTS.md
Codex reads AGENTS.md: paste the rule text there
Manually: put the files in place
.github/instructions/nuxt.instructions.mdThe applyTo field sets which files it applies to: move globs from the source into applyTo
Manually: put the files in place
.devin/rules/nuxt.mdManually: put the files in place
.clinerules/nuxt.mdStep by step, with commands for each platform: Cursor rules: .mdc files, four modes and what replaced .cursorrules
Questions about nuxt
How do I install the nuxt rule in Cursor?
Create .cursor/rules/nuxt.mdc in your project root and paste the rule into it. Cursor applies it to files matching **/*.{vue,js,ts}.
What does the nuxt rule do?
It gives the agent Nuxt conventions: directory layout, file naming, Composition API, Pinia usage, useFetch and useAsyncData, middleware for auth, and performance settings.
Does <NuxtImg> need the @nuxt/image module?
Yes. The rule calls for <NuxtImg> on images, and that component ships with @nuxt/image. Without the module a template using it will not build.
Source text
nuxt.mdc · CC0-1.0
---
description: This guide provides definitive, actionable best practices for building robust, performant, and secure Nuxt applications, emphasizing modern patterns and common pitfalls.
globs: **/*.{vue,js,ts}
---
# Nuxt Best Practices
This document outlines the essential guidelines for developing high-quality Nuxt applications. Adhere to these rules to ensure maintainable, performant, and secure code.
## 1. Code Organization and Structure
Nuxt's convention-over-configuration is a superpower. Leverage it fully.
### 1.1. Directory Structure
Always follow the Nuxt opinionated directory structure. Do not deviate.
- `components/`: Reusable Vue components.
- `composables/`: Reusable Composition API functions (`useFoo()`).
- `layouts/`: Application layouts.
- `middleware/`: Route middleware.
- `pages/`: Application pages (route definitions).
- `plugins/`: Nuxt.js plugins.
- `server/`: API routes and server-side logic.
- `store/`: Pinia stores.
- `utils/`: Generic utility functions.
### 1.2. File Naming Conventions
Consistency is key for auto-imports and readability.
- **Components**: `PascalCase.vue`
❌ BAD: `my-button.vue`
✅ GOOD: `MyButton.vue`
- **Composables**: `usePascalCase.ts`
❌ BAD: `fetchData.ts`
✅ GOOD: `useFetchData.ts`
- **Pages/Layouts/Middleware/Plugins/Stores**: `kebab-case.ts` or `kebab-case.vue`
❌ BAD: `UserProfile.vue` (for a page)
✅ GOOD: `user-profile.vue`
### 1.3. TypeScript Adoption
Enable TypeScript everywhere. It prevents bugs and improves developer experience.
- Configure `tsconfig.json` and ensure ESLint is set up for TypeScript.
- Always explicitly type Pinia stores, middleware, and composables.
- Run `nuxt prepare` or `nuxt dev` regularly to generate `imports.d.ts` for type safety.
## 2. Component Architecture
Design components for reusability, testability, and clarity.
### 2.1. Favor Composition over Inheritance
Use the Composition API to organize component logic.
❌ BAD:
```vue
<!-- MyComponent.vue -->
<script>
export default {
mixins: [myMixin], // Avoid mixins
// ...
}
</script>
```
✅ GOOD:
```vue
<!-- MyComponent.vue -->
<script setup lang="ts">
import { useMyFeature } from '~/composables/useMyFeature';
const { data, loading } = useMyFeature();
</script>
```
### 2.2. Use Slots for Flexibility
Slots make components adaptable without prop drilling.
❌ BAD:
```vue
<!-- MyCard.vue -->
<template>
<div class="card">
<h2 v-if="title">{{ title }}</h2>
<p v-if="description">{{ description }}</p>
<button v-if="showButton" @click="emit('action')">Action</button>
</div>
</template>
<script setup lang="ts">
defineProps<{ title?: string; description?: string; showButton?: boolean }>();
const emit = defineEmits(['action']);
</script>
```
✅ GOOD:
```vue
<!-- MyCard.vue -->
<template>
<div class="card">
<slot name="header" />
<slot /> <!-- Default slot for content -->
<slot name="actions" />
</div>
</template>
```
## 3. State Management (Pinia)
Pinia is the definitive state management solution for Nuxt.
### 3.1. Single Source of Truth & Immutability
Maintain a single, consistent source of truth. Always update state via actions/mutations.
❌ BAD:
```ts
// store/user.ts
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({ name: 'John Doe' }),
actions: {
updateNameDirectly(newName: string) {
this.name = newName; // Direct mutation outside action/mutation
},
},
});
```
✅ GOOD:
```ts
// store/user.ts
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({ name: 'John Doe' }),
actions: {
setName(newName: string) {
this.name = newName; // Mutation within an action
},
},
});
```
### 3.2. Modularization
Organize stores by feature.
```ts
// store/auth.ts
export const useAuthStore = defineStore('auth', { /* ... */ });
// store/products.ts
export const useProductsStore = defineStore('products', { /* ... */ });
```
## 4. Common Patterns and Anti-patterns
### 4.1. Data Fetching
Always use Nuxt's built-in data fetching composables for SSR compatibility and deduplication.
- `useFetch`: For direct API calls.
- `useAsyncData`: For custom async operations.
❌ BAD:
```vue
<script setup lang="ts">
import { ref, onMounted } from 'vue';
const data = ref(null);
onMounted(async () => {
data.value = await $fetch('/api/items'); // Not SSR-friendly, fetches twice
});
</script>
```
✅ GOOD:
```vue
<script setup lang="ts">
const { data } = await useFetch('/api/items'); // SSR-friendly, fetches once
</script>
```
### 4.2. Error Handling
Implement robust error handling.
- **Centralized**: Use `NuxtError` component for global errors.
- **API Errors**: Always catch and handle errors from `useFetch`/`useAsyncData`.
```vue
<script setup lang="ts">
const { data, error } = await useFetch('/api/data');
if (error.value) {
// Handle specific error, e.g., redirect or show message
console.error('Failed to fetch data:', error.value);
throw createError({ statusCode: 500, statusMessage: 'Could not load data' });
}
</script>
```
### 4.3. Authentication & Authorization
Use Nuxt middleware for route protection.
```ts
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
const userStore = useUserStore();
if (!userStore.isAuthenticated && to.path !== '/login') {
return navigateTo('/login');
}
});
```
## 5. Performance Considerations
Optimize for Core Web Vitals.
### 5.1. Smart Links
Use `<NuxtLink>` for all internal navigation. Configure `prefetchOn: 'interaction'` for optimal performance.
```ts
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
defaults: {
nuxtLink: {
prefetchOn: 'interaction', // Prefetch only on user interaction
},
},
},
});
```
### 5.2. Hybrid Rendering
Leverage `routeRules` for granular control over page rendering.
```ts
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true }, // Static generation at build time
'/products/**': { swr: 3600 }, // Stale-While-Revalidate for dynamic content
'/admin/**': { ssr: false }, // Client-side rendering for authenticated sections
},
});
```
### 5.3. Lazy Loading Components
Prefix components with `Lazy` to defer loading.
❌ BAD:
```vue
<template>
<HeavyComponent v-if="show" />
</template>
```
✅ GOOD:
```vue
<template>
<LazyHeavyComponent v-if="show" />
</template>
```
### 5.4. Image Optimization
Always use `@nuxt/image` and `<NuxtImg>`.
```vue
<template>
<!-- Critical image, load ASAP -->
<NuxtImg
src="/hero.jpg"
format="webp"
preload
loading="eager"
fetch-priority="high"
width="1200"
height="600"
/>
<!-- Non-critical image, lazy load -->
<NuxtImg
src="/gallery/item.jpg"
format="webp"
loading="lazy"
fetch-priority="low"
width="400"
height="300"
/>
</template>
```
## 6. Accessibility (A11y)
Build inclusive applications from the start.
- Use semantic HTML elements.
- Provide `alt` text for all images.
- Ensure keyboard navigation is fully functional.
- Manage focus for modals and dynamic content.
- Maintain sufficient color contrast.
## 7. Security Best Practices
Assume breach and validate everything.
### 7.1. Server-Side Input Validation
Always validate all incoming data on the server. Client-side validation is for UX, not security.
```ts
// server/api/users.post.ts
import { z } from 'zod'; // Use Zod for robust schema validation
const userSchema = z.object({
email: z.string().email().max(255),
password: z.string().min(8),
});
export default defineEventHandler(async (event) => {
try {
const body = await readBody(event);
const validatedData = userSchema.parse(body);
// Proceed with validatedData
return { status: 'success', data: validatedData };
} catch (error) {
if (error instanceof z.ZodError) {
throw createError({ statusCode: 400, message: 'Invalid input', data: error.errors });
}
throw createError({ statusCode: 500, message: 'Server error' });
}
});
```
### 7.2. Content Sanitization
Sanitize any user-generated HTML before rendering.
```ts
// utils/sanitize.ts
import DOMPurify from 'isomorphic-dompurify';
export function sanitizeHtml(dirtyHtml: string): string {
return DOMPurify.sanitize(dirtyHtml, {
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'p', 'br', 'a'],
ALLOWED_ATTR: ['href', 'target'],
});
}
```
### 7.3. Secure Cookies
For authentication tokens, use `httpOnly`, `secure`, and `sameSite` flags.
```ts
// server/api/auth/login.post.ts
export default defineEventHandler(async (event) => {
// ... authentication logic ...
setCookie(event, 'auth_token', 'your_jwt_token', {
httpOnly: true, // Prevent client-side JS access
secure: process.env.NODE_ENV === 'production', // Only send over HTTPS in production
sameSite: 'lax', // CSRF protection
maxAge: 60 * 60 * 24 * 7, // 7 days
path: '/',
});
return { message: 'Logged in' };
});
```
### 7.4. Avoid Dangerous JavaScript Patterns
Never execute user input as code.
❌ BAD: `eval(userInput)`, `new Function(userInput)`, `element.innerHTML = userInput`
✅ GOOD: `JSON.parse(userInput)`, `element.textContent = userInput`
## 8. Common Pitfalls and Gotchas
- **Forgetting `nuxt prepare`**: If TypeScript errors appear for auto-imports, run `npx nuxt prepare`.
- **Heavy async work in module setup**: Defer time-consuming logic to Nuxt hooks (`onInstall`, `onUpgrade`) or runtime hooks, not the module's `setup` function.
- **Not prefixing module exports**: Custom modules must prefix all exposed APIs, components, and composables (e.g., `useMyModuleFoo`).
- **Directly mutating props**: Always emit events to update parent state.
- **Ignoring server-side security**: Never trust client input; validate and sanitize on the server.
© sanjeed5 · CC0-1.0 · sanjeed5/awesome-cursor-rules-mdc