{{ data.title }}
\n{{ data.content }}
\n{{ data.content }}
\n{{ error.message }}
\nNuxt 3 beginner complete: Vue.js framework production-ready, step-by-step tutorial, server-side rendering resolved, auto-routing essentials. Encyclopedic reference for new developers learning Nuxt.
Last Update: 2025-12-03 - Created: 2025-12-03
Production-ready compilation flags and build commands
Copy → Paste → Live
Local development server running at http://localhost:3000 with hot module replacement. Learn more in Nuxt project setup section
Decision matrix per scegliere la tecnologia giusta
Building full-stack Vue.js applications with Nuxt 3 framework and automatic file-based routing
Server-side rendering (SSR) projects requiring SEO optimization and faster initial page loads
Static site generation using Nuxt content collections with markdown and JSON data sources
Real-time WebSocket applications requiring constant bidirectional communication
Mobile-first native applications without server infrastructure
Micro-frontend architectures where Nuxt monolithic structure creates overhead
Production-ready compilation flags and build commands
Nuxt beginner fundamental: pages/ directory automatically generates Vue.js routes without manual routing configuration. Files in pages/ map to URL paths (pages/about.vue → /about, pages/blog/[id].vue → /blog/:id). See dynamic routes examples below
Placing components in pages/ directory expecting them not to become routes
Use components/ directory for reusable Vue components, pages/ only for route endpointsServer-side rendering enabled by default in Nuxt 3 framework. HTML pre-rendered on server before sending to client browser. Improves SEO and First Contentful Paint (FCP) metrics. See SSR optimization best practices
Using browser-only APIs (window, document) in component setup without universal checks
Wrap browser APIs in process.client conditional or use Nuxt hooksVue 3 composables imported from composables/ directory enable DRY (Don't Repeat Yourself) code patterns. useCart(), useFetch(), useAsyncData() examples. Nuxt auto-imports composables, reducing boilerplate. See reusable state management examples
Nuxt framework includes built-in server capabilities. Files in server/api/ directory become Express.js-compatible endpoints (/api/users → server/api/users.ts). Full-stack development in single project. See API route best practices for production
Exposing sensitive environment variables in client-side API route responses
Use server-only environment variables prefixed with NUXT_SECRET_ or process.env checksMiddleware functions execute before page rendering. Protection pattern: auth middleware redirects unauthenticated users. Route middleware applies to specific pages, global middleware applies universally. See middleware authentication flow examples

The ultimate collection of production-ready developer cheatsheets. Copy, paste, ship. Built for developers who value their time.
Copy-paste ready code blocks with real-world use cases
Production-ready compilation flags and build commands
npx nuxi@latest init <project-name> [--git] [--install]npm run dev [--open] [--port 3000] [--host 0.0.0.0]npm run build [--dotenv .env.production]npm run preview [--port 3000]npm run generate [--dotenv .env.production]Create pages/<path>.vue file; Nuxt auto-routesCreate middleware/<name>.ts and use defineRouteMiddleware()Create server/api/<path>.ts with defineEventHandler()Enable experimental.appManifest in nuxt.config.tsexport default defineNuxtConfig({ /* options */ })Production-tested solutions for common errors (2500+ cases resolved)
Using browser APIs in universal component or middleware without checking runtime environment
Wrap browser API calls in process.client condition: if (process.client) { window.gtag(...) } or use useAsyncData with server: false optionAPI route file named incorrectly or placed outside server/api/ directory, or using GET fetch instead of POST-specific handler file
Ensure server/api/ route files match request patterns: server/api/users/[id].ts for /api/users/:id requests, use .post.ts/.get.ts suffixes if neededModule imported in nuxt.config.ts modules array but not installed, or version incompatibility with Nuxt 3
Install module explicitly: npm install @nuxt/content, verify version compatibility with Nuxt 3.x in package.jsonDynamic list rendering without stable keys or mismatched data between server and client render, or improper use of conditional rendering
Add unique keys to v-for loops using data IDs not indices: v-for="item in items" :key="item.id", ensure data state identical on server/client initLarge project with many routes or data, or node --max-old-space-size not configured for build process, memory leak in build plugins
Increase Node.js heap: NODE_OPTIONS=--max-old-space-size=4096 npm run build, split code into multiple entry points, optimize build pluginsCause: Using process.env directly in client components; environment variables not available on browser runtime
Avg fix time
Use useRuntimeConfig() composable for NUXT_PUBLIC_ prefixed variables, or access via API endpoint for secret variablesCause: Middleware file placed in wrong directory (middleware/ instead of server/middleware/), or middleware name doesn't match definePageMeta reference
Avg fix time
Verify middleware/ directory at project root, confirm definePageMeta({ middleware: 'exact-name' }) matches middleware filename exactlyCause: useAsyncData caching key doesn't include route parameter, or missing watch configuration
Avg fix time
Include route.params in cache key: useAsyncData(`blog-${route.params.slug}`, ...), add watch: [() => route.params.slug]Cause: Component file name doesn't match import statement, or component placed outside components/ directory without explicit registration
Avg fix time
Verify exact filename matches import name (PascalCase for .vue files), ensure components in components/ directory for auto-importCause: API route file not named with correct method suffix (.post.ts, .get.ts), or using wrong HTTP method in client fetch
Avg fix time
Name file server/api/users.post.ts for POST requests, or use defineEventHandler that checks getHeader(event, 'method')Cause: Composable uses browser-only APIs (localStorage, window) without checking process.client condition
Avg fix time
Wrap browser APIs in if (process.client) { ... }, or use onMounted() hook which only executes client-sideCause: Multiple large dependencies imported globally, auto-import includes unused components, no code splitting strategy
Avg fix time
Enable code splitting via route-based chunks, use dynamic imports for heavy features, audit bundle with nuxi analyzeCause: Store not properly initialized, or reactive values not declared with ref() or reactive()
Avg fix time
Ensure store returns reactive values from setup function, persist critical state to localStorage if neededCause: Images lack width/height attributes, or using NuxtImg without explicit dimensions
Avg fix time
Add width and height attributes to all img tags, use NuxtImg component with width/height propsCause: Dev server cache not cleared, or changes made to non-watched files (server/ directory requires restart)
Avg fix time
Restart dev server with npm run dev, changes in server/ require full restart; client changes auto-updateCommon Nuxt beginner errors with root cause analysis and verified fixes
Build fails during npm run dev or npm run build
@nuxt/content module listed in nuxt.config.ts but not installed via npm/yarn
npm install @nuxt/content@latest && npm run devPage crashes on load with error in route handling
useRoute() called outside of component/composable context, or route not yet loaded during SSR
Ensure useRoute() is in component or composable called after component mounted, use watch if route-dependentPage renders but hydration fails, Vue warnings in console
Server-rendered HTML differs from client-rendered content (mismatched data, dynamic content without keys, or floating point values)
Ensure v-for has stable :key, check Date.now() isn't used (non-deterministic), wrap client-only content in <ClientOnly>API endpoint returns 500 without helpful error message
Unhandled exception in API route handler, missing error handling, or incorrect response format
Add try-catch block to API route, log errors to server terminal, return proper JSON error responseImages show broken icon or don't appear on page
Image path incorrect or images not in public/ directory, or NuxtImg component path mismatch
Place images in public/ folder, reference as /images/photo.jpg (absolute path), or use NuxtImg with correct src propData variable undefined or null, page shows loading state indefinitely
Async fetch function throws error (network, parsing), or data not awaited before render
Check error property of useAsyncData, verify fetch endpoint returns valid JSON, check server logs for 5xx errorsError: 'Cannot read properties of undefined (reading 'state')'
Store not imported with useXxxStore(), or Pinia plugin not registered in nuxt.config.ts
Import store: const store = useCartStore(), verify @pinia/nuxt in modules arraynpm run dev hangs or is very slow during startup
Large number of components/routes causing analysis slowdown, or TypeScript type checking taking time
Upgrade to latest Nuxt version, increase Node heap: NODE_OPTIONS=--max-old-space-size=4096, disable TypeScript checking if not neededCI/CD pipeline times out during npm run build
Build process too slow due to large project size, or Vercel/Netlify has strict time limits
Optimize build: disable unused modules in nuxt.config.ts, use npm ci instead of npm install, cache node_modules in CIAPI requests from other domain fail with CORS error
API endpoint doesn't set CORS headers, or credentials not properly configured
Add CORS middleware in server/middleware/, set Access-Control-Allow-Origin header, or configure origin whitelistAdvanced performance tips and optimizations for Nuxt beginner
// nuxt.config.ts
export default defineNuxtConfig({
components: {
dirs: [
{
path: '~/components/global',
global: true
},
{
path: '~/components/layout',
prefix: 'Layout'
}
]
},
imports: {
dirs: ['./composables/**']
}
})
// Analyze bundle: npx nuxi analyze
// Only globally-used components added to bundle, others tree-shaken// app.vue
<template>
<NuxtLayout>
<Suspense>
<NuxtPage />
<template #fallback>
<div class="skeleton">Loading...</div>
</template>
</Suspense>
</NuxtLayout>
</template>
// Prevents CLS (Cumulative Layout Shift) with skeleton UI fallback// server/utils/cache.ts
const requestCache = new Map()
export const cachedFetch = async (key, fetcher, options = {}) => {
if (requestCache.has(key)) return requestCache.get(key)
const promise = fetcher().then(result => {
setTimeout(() => requestCache.delete(key), options.ttl || 1000)
return result
})
requestCache.set(key, promise)
return promise
}
// server/api/data.ts
export default defineEventHandler(() => {
return cachedFetch('data-key', () => expensiveFetch(), { ttl: 5000 })
})
// Multiple simultaneous /api/data requests return same promise// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
payloadExtraction: false
},
app: {
pageTransition: { name: 'page', mode: 'out-in' }
}
})
// Add to component
<template>
<NuxtLink to="/about" prefetch>About</NuxtLink>
</template>
<!-- Automatically prefetches /about route chunk when link intersects viewport -->
<!-- Uses requestIdleCallback for zero-impact prefetch -->
// nuxt.config.ts
export default defineNuxtConfig({
experimental: {
payloadExtraction: true // Enable in Nuxt 3.2+
}
})
// pages/blog/[slug].vue
const { data: post } = await useAsyncData(
`blog-${route.params.slug}`,
() => queryContent(`blog/${route.params.slug}`).findOne()
)
// HTML contains pre-rendered post data inline, no separate /api/data request needed
// Saves 1 HTTP round-trip on page loadComplete CI/CD pipelines from prototype to production deployment for Nuxt beginner
cat .env.production && grep -E '^NUXT_' .env.productionnpm run buildnpm run previewgit push origin main # Triggers CI/CD pipeline// Open DevTools: F12 or Cmd+Shift+I, look for red errors// Terminal running npm run dev should show error messages// Create missing file or correct import
echo 'export const useMissingComposable = () => ref(null)' > composables/useMissingComposable.ts// Hard refresh: Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows)git add . && git commit -m 'Add new feature' && git push origin feature-branch# .github/workflows/test.yml triggered automatically
npm run lint && npm run testnpm run build # Runs in CI environment# Vercel preview deployment automatically triggered// Installed via npm, Sentry captures client errors automatically
// In nuxt.config.ts: modules: ['@sentry/nuxt/module']// Google Analytics captures LCP, FID, CLS automatically with Nuxt
// Pages report vital metrics to GA4 dashboard// Sentry alert: Error rate > 5% in 5-minute window → Slack notification// Dashboard shows: page views, error count, performance trendsnpm audit && npm audit fix// server/middleware/csp.ts
export default defineEventHandler((event) => {
setHeader(event, 'Content-Security-Policy', "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.example.com")
})// All POST/PUT/DELETE endpoints validate CSRF token from requestnpm audit --production && npm run security:checkHardware: AWS t3.medium (2 vCPU, 4GB RAM), Nuxt 3.8.1, Node.js 20.10, Optimizations: Route caching (SWR 1h), Async data deduplication, Image lazy-loading, gzip compression enabled
Real-world applications with measured performance metrics
Complete blog application with server-side rendering for SEO, API routes for backend, Pinia for state management, middleware for authentication, and markdown content rendering.
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/content', '@pinia/nuxt'],
content: {
documentDriven: true
},
app: {
head: {
title: 'My Blog',
meta: [{ name: 'description', content: 'Blog about web development' }]
}
}
})
// server/api/posts.ts
export default defineEventHandler(async () => {
return { posts: await queryContent('blog').find() }
})
// pages/blog/[slug].vue
<template>
<article v-if="post" class="prose">
<h1>{{ post.title }}</h1>
<p class="text-gray-500">{{ formatDate(post.date) }}</p>
<ContentRenderer :value="post" />
</article>
</template>
<script setup>
const route = useRoute()
definePageMeta({ middleware: 'log-views' })
const { data: post } = await useAsyncData(`blog-${route.params.slug}`, () => queryContent(`blog/${route.params.slug}`).findOne())
</script>
// stores/blog.ts
export const useBlogStore = defineStore('blog', () => {
const posts = ref([])
const categories = ref([])
const fetchPosts = async () => {
posts.value = await $fetch('/api/posts')
categories.value = [...new Set(posts.value.map(p => p.category))]
}
return { posts, categories, fetchPosts }
})
E-commerce application with product listing, dynamic filtering, Pinia cart management, server-side search API, and checkout flow with middleware protection.
// server/api/products.ts
export default defineEventHandler(async (event) => {
const query = getQuery(event)
const { category, minPrice = 0, maxPrice = 999999 } = query
const products = await db.products.find({
category: category ? category : undefined,
price: { $gte: minPrice, $lte: maxPrice }
})
return { products, total: products.length }
})
// stores/cart.ts
export const useCartStore = defineStore('cart', () => {
const items = ref([])
const subtotal = computed(() => items.value.reduce((sum, item) => sum + (item.price * item.quantity), 0))
const addToCart = (product, quantity = 1) => {
const existing = items.value.find(i => i.id === product.id)
if (existing) existing.quantity += quantity
else items.value.push({ ...product, quantity })
}
const removeFromCart = (productId) => {
items.value = items.value.filter(i => i.id !== productId)
}
return { items, subtotal, addToCart, removeFromCart }
})
// pages/products.vue
<template>
<div class="grid gap-4">
<div class="sidebar">
<FilterPanel v-model="filters" @change="fetchProducts" />
</div>
<div class="products">
<ProductCard v-for="product in products" :key="product.id" :product="product" @add-to-cart="addToCart" />
</div>
</div>
</template>
<script setup>
const filters = reactive({ category: '', minPrice: 0, maxPrice: 999999 })
const cart = useCartStore()
const { data: products } = await useFetch('/api/products', { query: filters })
const addToCart = (product) => {
cart.addToCart(product)
showNotification('Added to cart')
}
</script>
Admin dashboard with real-time metrics, chart visualization, user data tables, and WebSocket updates using Nuxt server capabilities.
// server/api/analytics.ts
export default defineEventHandler(async (event) => {
const startDate = getQuery(event).startDate
const metrics = await db.analytics.aggregate([
{ $match: { date: { $gte: new Date(startDate) } } },
{ $group: { _id: '$date', visits: { $sum: 1 }, revenue: { $sum: '$amount' } } }
])
return { metrics }
})
// composables/useAnalytics.ts
export const useAnalytics = () => {
const metrics = ref([])
const loading = ref(false)
const fetchMetrics = async (dateRange) => {
loading.value = true
metrics.value = await $fetch('/api/analytics', { query: { startDate: dateRange } })
loading.value = false
}
return { metrics, loading, fetchMetrics }
}
// pages/admin/dashboard.vue
<template>
<div class="dashboard">
<div class="grid grid-cols-4 gap-4">
<StatCard title="Total Visits" :value="totalVisits" trend="+12%" />
<StatCard title="Revenue" :value="totalRevenue" trend="+8%" />
<StatCard title="Conversions" :value="conversions" trend="+5%" />
<StatCard title="AOV" :value="aov" trend="+3%" />
</div>
<Chart :data="metrics" type="line" />
</div>
</template>
<script setup>
definePageMeta({ middleware: 'admin-auth' })
const { metrics, fetchMetrics } = useAnalytics()
const totalVisits = computed(() => metrics.value.reduce((sum, m) => sum + m.visits, 0))
const totalRevenue = computed(() => metrics.value.reduce((sum, m) => sum + m.revenue, 0))
onMounted(() => fetchMetrics(new Date(Date.now() - 30 * 24 * 60 * 60 * 1000)))
</script>
Hybrid rendering strategy combining static generation for public pages and SSR for dynamic content, with ISR (Incremental Static Regeneration) for frequent updates.
// nuxt.config.ts
export default defineNuxtConfig({
routeRules: {
// Prerendered static pages
'/': { prerender: true },
'/about': { prerender: true },
// SSR with caching
'/blog/**': { swr: 3600 }, // Cache for 1 hour, serve stale while revalidating
'/products/**': { cache: { maxAge: 60 * 60 } },
// API caching
'/api/**': { cache: { maxAge: 60 } },
// SPA mode for dynamic content
'/admin/**': { ssr: false }
}
})
// pages/products/[id].vue
<template>
<div>
<h1>{{ product.name }}</h1>
<Price :value="product.price" />
</div>
</template>
<script setup>
const route = useRoute()
const { data: product } = await useAsyncData(
`product-${route.params.id}`,
() => $fetch(`/api/products/${route.params.id}`),
{ watch: [route] }
)
</script>
Multilingual website with language switching, automatic locale detection, translated content, and SEO-friendly URL structure for each language.
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxtjs/i18n'],
i18n: {
locales: [
{ code: 'en', iso: 'en-US', file: 'en.json' },
{ code: 'it', iso: 'it-IT', file: 'it.json' },
{ code: 'fr', iso: 'fr-FR', file: 'fr.json' }
],
defaultLocale: 'en',
strategy: 'prefix',
lazy: true,
langDir: 'locales/'
}
})
// locales/en.json
{ "hello": "Hello", "welcome": "Welcome to our site" }
// locales/it.json
{ "hello": "Ciao", "welcome": "Benvenuto nel nostro sito" }
// pages/index.vue
<template>
<div>
<h1>{{ $t('welcome') }}</h1>
<LanguageSwitcher />
</div>
</template>
<script setup>
const { locale, locales } = useI18n()
const availableLocales = locales.value.map(l => ({ code: l.code, name: l.name }))
</script>
Progressive Web App with offline support, service workers, installable app, push notifications, and offline-first data sync using Nuxt PWA module.
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@nuxt/pwa'],
pwa: {
manifest: {
name: 'My App',
short_name: 'App',
start_url: '/',
display: 'standalone',
scope: '/',
theme_color: '#000000',
background_color: '#ffffff'
},
workbox: {
runtimeCaching: [
{ urlPattern: '/api/**', handler: 'NetworkFirst' },
{ urlPattern: '/images/**', handler: 'CacheFirst' }
]
}
}
})
// composables/useOfflineSync.ts
export const useOfflineSync = () => {
const syncData = async (endpoint, data) => {
if (!navigator.onLine) {
// Store in IndexedDB for later sync
const db = await openDB('app-sync')
await db.add('queue', { endpoint, data, timestamp: Date.now() })
return
}
await $fetch(endpoint, { method: 'POST', body: data })
}
return { syncData }
}
// pages/offline-feature.vue
<template>
<div>
<div v-if="!navigator.onLine" class="offline-banner">
You are offline. Changes will sync when online.
</div>
<button @click="submitForm">Submit ({{ isOnline ? 'online' : 'offline' }})</button>
</div>
</template>
<script setup>
const { syncData } = useOfflineSync()
const isOnline = ref(navigator.onLine)
onMounted(() => {
window.addEventListener('online', () => { isOnline.value = true })
window.addEventListener('offline', () => { isOnline.value = false })
})
const submitForm = async () => {
await syncData('/api/form', formData.value)
}
</script>