Introduction
In the rapidly evolving world of web development, staying up-to-date with the latest practices is crucial. For developers working with Vue.js and Nuxt.js, adhering to modern coding standards is essential to ensure efficiency and performance. Here, we explore 13 key rules that guide AI in generating composable, server-side rendering (SSR) ready components, tailored for Vue 3 and Nuxt 3.
1. Embrace the Composition API
Use the Composition API with <script setup> exclusively. Avoid the Options API, as it does not scale well with new features and complicates TypeScript usage. Mixins are deprecated in favor of composables, which offer better type checking.
<script setup lang="ts">
const props = defineProps<{ userId: string }>()
const { data: user } = await useFetch(`/api/users/${props.userId}`)
</script>
2. Composables: Functionality Without Rendering
Composables should function as nouns, like useUser() or useCart(), returning reactive states without handling rendering. Always return refs to maintain reactivity.
// composables/useUser.ts
export function useUser(id: Ref<string>) {
const { data, pending, error } = useFetch(() => `/api/users/${id.value}`)
return { user: data, pending, error }
}
3. Maintain Reactivity
Opt for ref for primitive values and reactive only for stable object structures. Avoid destructuring reactive, which disrupts reactivity.
4. Use Pinia for State Management
Adopt Pinia with setup-store syntax, creating one store per domain. This approach eliminates the need for Vuex's complex mutations and modules.
// stores/cart.ts
export const useCartStore = defineStore('cart', () => {
const items = ref<CartItem[]>([])
const total = computed(() => items.value.reduce((s, i) => s + i.price, 0))
function add(item: CartItem) { items.value.push(item) }
return { items, total, add }
})
5. Optimize Data Fetching
Fetch data on the server using useFetch or useAsyncData to improve initial load times instead of using onMounted.
<script setup lang="ts">
const route = useRoute()
const { data: post } = await useAsyncData(
`post-${route.params.id}`,
() => $fetch(`/api/posts/${route.params.id}`)
)
</script>
6. Secure Server Routes
Place server routes in server/api/, using Zod for validation and ensuring end-to-end typing.
// server/api/posts.post.ts
import { z } from 'zod'
const Body = z.object({ title: z.string().min(1).max(120) })
export default defineEventHandler(async (event) => {
const session = await requireUserSession(event)
const body = Body.parse(await readBody(event))
return await db.post.create({ data: { ...body, userId: session.user.id } })
})
7. Enforce TypeScript Strictness
Use <script setup lang="ts"> with strict TypeScript settings to catch potential errors early.
8. Clean Template Practices
Avoid using v-if and v-for on the same element, and ensure keys are stable IDs.
9. Respect Prop Immutability
Props should remain read-only. Use events or defineModel() for two-way data binding when necessary.
10. Utilize Auto-Imports
Leverage Nuxt's auto-import feature for cleaner code and fewer merge conflicts.
11. Manage Error and Loading States
Implement a root error.vue and use <NuxtLoadingIndicator> for better UX during loading.
12. Prioritize Testing
Adopt Vitest and @vue/test-utils, focusing on real component mounting and role-based querying.
13. Enhance Performance
Use <NuxtImage>, <NuxtLink>, and lazy-load heavy components to optimize performance.