| name | nuxt-4 |
| description | Nuxt 4 patterns, composables, server routes, data fetching. Trigger: When writing Nuxt code - pages, composables, server routes, useFetch, layouts.
|
| license | Apache-2.0 |
| metadata | {"author":"gentleman-programming","version":"1.0"} |
When to Use
- Creating Nuxt pages, layouts, components
- Writing composables or server routes (Nitro)
- Data fetching with useFetch, useAsyncData, $fetch
- Middleware (route or server)
- SEO with useSeoMeta/useHead
- State management with useState or Pinia
File Structure (Nuxt 4 app/ directory)
project/
āāā app/ # All app code
ā āāā components/ # Auto-imported components
ā āāā composables/ # Auto-imported composables
ā āāā layouts/ # Layouts (default.vue, dashboard.vue)
ā āāā middleware/ # Route middleware
ā āāā pages/ # File-based routing
ā āāā plugins/ # Nuxt plugins
ā āāā utils/ # Auto-imported utilities
ā āāā app.vue # Root component
āāā server/ # Nitro server
ā āāā api/ # API routes (/api prefix)
ā āāā routes/ # Server routes (no prefix)
ā āāā middleware/ # Server middleware (use sparingly)
ā āāā utils/ # Server utilities
āāā shared/ # Shared between client/server
ā āāā utils/
ā āāā types/
āāā public/ # Static files
āāā nuxt.config.ts
Critical Patterns
Data Fetching Decision Tree (CRITICAL)
Component setup, initial load? ā useFetch()
Custom async logic, multiple calls ā useAsyncData()
Client-only (button, form submit) ā $fetch()
Don't block navigation? ā useLazyFetch() / useLazyAsyncData()
Need nested reactivity (forms)? ā useFetch('/api/x', { deep: true })
useFetch (Primary ā SSR + Client)
const { data, pending, error, refresh } = await useFetch('/api/users')
const id = ref(1)
const { data } = await useFetch(`/api/users/${id}`)
const { data } = await useFetch('/api/users', {
pick: ['id', 'name', 'email']
})
const { data } = await useFetch('/api/users', {
transform: (users) => users.map(u => ({ ...u, fullName: `${u.first} ${u.last}` }))
})
const { data } = await useFetch('/api/complex', { deep: true })
Nuxt 4: Returns shallowRef by default. Use deep: true ONLY when mutating nested properties.
useAsyncData (Custom async)
const { data } = await useAsyncData('user-with-posts', async () => {
const user = await $fetch('/api/user')
const posts = await $fetch('/api/posts')
return { user, posts }
})
$fetch (Client-only mutations)
async function handleSubmit() {
await $fetch('/api/submit', { method: 'POST', body: formData })
}
$fetch Plugin (API Instance)
export default defineNuxtPlugin(() => {
const api = $fetch.create({
baseURL: '/api',
onRequest({ options }) {
const token = useCookie('token')
if (token.value) {
options.headers = { ...options.headers, Authorization: `Bearer ${token.value}` }
}
},
onResponseError({ response }) {
if (response.status === 401) navigateTo('/login')
}
})
return { provide: { api } }
})
Server Routes (Nitro)
export default defineEventHandler(async (event) => {
return await db.users.findMany()
})
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id')
const user = await db.users.findUnique({ where: { id } })
if (!user) throw createError({ statusCode: 404, message: 'User not found' })
return user
})
export default defineEventHandler(async (event) => {
const body = await readBody(event)
return await db.users.create({ data: body })
})
Route Rules (nuxt.config.ts)
export default defineNuxtConfig({
routeRules: {
'/api/cached': { swr: 60 },
'/api/static': { prerender: true },
'/admin/**': { ssr: false },
}
})
Middleware
Route Middleware (Vue layer)
export default defineNuxtRouteMiddleware((to, from) => {
const user = useState('user')
if (!user.value) return navigateTo('/login')
})
definePageMeta({ middleware: 'auth' })
Server Middleware (Use sparingly ā anti-pattern for most cases)
export default defineEventHandler((event) => {
console.log('Request:', event.path)
})
State Management
useState (Simple, SSR-safe)
const counter = useState('counter', () => 0)
const user = useState<User>('user', () => ({ name: '', email: '' }))
Pinia (Computed state, devtools, complex mutations)
export const useUserStore = defineStore('user', () => {
const user = ref<User | null>(null)
const isAuthenticated = computed(() => !!user.value)
async function login(credentials: Credentials) {
const data = await $fetch('/api/login', { method: 'POST', body: credentials })
user.value = data.user
}
return { user, isAuthenticated, login }
})
Decision: Simple state (flags, cookies) ā useState. Derived state, devtools ā Pinia.
SEO
useSeoMeta({
title: 'My Page',
description: 'Page description',
ogTitle: 'OG Title',
ogImage: 'https://example.com/image.jpg',
})
useHead({
link: [{ rel: 'icon', href: '/favicon.ico' }],
titleTemplate: (title) => title ? `${title} - My Site` : 'My Site'
})
Error Handling
throw createError({ statusCode: 404, message: 'Not found' })
showError({ statusCode: 500, message: 'Something broke' })
<NuxtErrorBoundary>
<SomeComponent />
<template #error="{ error, clearError }">
<p>{{ error }}</p>
<button @click="clearError">Retry</button>
</template>
</NuxtErrorBoundary>
Nuxt 4 Breaking Changes
- shallowRef default ā useFetch/useAsyncData return shallowRef (use
deep: true for nested reactivity)
- Data cleared on refetch ā
data.value = null during refresh (not stale data)
app/ directory ā Optional but recommended
shared/ directory ā Auto-imported between client and server
Commands
nuxi dev
nuxi build
nuxi generate
nuxi preview
nuxi typecheck
nuxi prepare
nuxi cleanup
nuxi analyze