| name | nuxt-core |
| description | | Nuxt 4 core framework fundamentals: project setup, configuration, routing, SEO, error handling, and directory structure. Use when: creating new Nuxt 4 projects, configuring nuxt.config.ts, setting up routing and middleware, implementing SEO with useHead/useSeoMeta, handling errors with error.vue and NuxtErrorBoundary, or understanding Nuxt 4 directory structure. |
| license | MIT |
| metadata | {"version":"4.0.0","author":"Claude Skills Maintainers","category":"Framework","framework":"Nuxt","framework-version":"4.x","last-verified":"2025-12-28T00:00:00.000Z","keywords":["Nuxt 4","nuxt.config.ts","file-based routing","pages directory","definePageMeta","useHead","useSeoMeta","error.vue","NuxtErrorBoundary","middleware","navigateTo","NuxtLink","app directory","runtime config"]} |
Nuxt 4 Core Fundamentals
Project setup, configuration, routing, SEO, and error handling for Nuxt 4 applications.
Quick Reference
Version Requirements
| Package | Minimum | Recommended |
|---|
| nuxt | 4.0.0 | 4.2.x |
| vue | 3.5.0 | 3.5.x |
| nitro | 2.10.0 | 2.12.x |
| vite | 6.0.0 | 6.2.x |
| typescript | 5.0.0 | 5.x |
Key Commands
bunx nuxi@latest init my-app
bun run dev
bun run build
bun run preview
bun run postinstall
bunx nuxi typecheck
bunx nuxi add page about
bunx nuxi add component MyButton
bunx nuxi add composable useAuth
Secure Installation
Scaffolding tools like bunx nuxi init download and execute remote code. Before running, follow supply chain security best practices:
- Block post-install scripts —
npm config set ignore-scripts true (or Bun: disabled by default)
- Cooldown period — Wait 7 days for new package versions to be vetted by the community
- Audit before installing — Run
socket package score npm <pkg> or use socket npm install <pkg> to check packages
Load the dependency-upgrade skill for full security configuration including Socket CLI integration, cooldown setup, lockfile validation, and CI enforcement.
Directory Structure (Nuxt v4)
my-nuxt-app/
├── app/ # Default srcDir in v4
│ ├── assets/ # Build-processed assets (CSS, images)
│ ├── components/ # Auto-imported Vue components
│ ├── composables/ # Auto-imported composables
│ ├── layouts/ # Layout components
│ ├── middleware/ # Route middleware
│ ├── pages/ # File-based routing
│ ├── plugins/ # Nuxt plugins
│ ├── utils/ # Auto-imported utility functions
│ ├── app.vue # Main app component
│ ├── app.config.ts # App-level runtime config
│ ├── error.vue # Error page component
│ └── router.options.ts # Router configuration
│
├── server/ # Server-side code (Nitro)
│ ├── api/ # API endpoints
│ ├── middleware/ # Server middleware
│ ├── plugins/ # Nitro plugins
│ ├── routes/ # Server routes
│ └── utils/ # Server utilities
│
├── public/ # Static assets (served from root)
├── shared/ # Shared code (app + server)
├── content/ # Nuxt Content files (if using)
├── layers/ # Nuxt layers
├── modules/ # Local modules
├── .nuxt/ # Generated files (git ignored)
├── .output/ # Build output (git ignored)
├── nuxt.config.ts # Nuxt configuration
├── tsconfig.json # TypeScript configuration
└── package.json # Dependencies
Key Change in v4: The app/ directory is now the default srcDir. All app code goes in app/, server code stays in server/.
When to Load References
Load references/configuration-deep.md when:
- Configuring advanced nuxt.config.ts options
- Setting up modules and plugins
- Customizing Vite or Nitro configuration
- Configuring experimental features
Load references/routing-advanced.md when:
- Implementing complex routing patterns
- Creating route middleware with authentication
- Using catch-all routes or optional parameters
- Configuring router options
Load references/plugins-architecture.md when:
- Creating Nuxt plugins
- Injecting global utilities or composables
- Integrating third-party libraries
- Understanding plugin execution order
Configuration
Basic nuxt.config.ts
export default defineNuxtConfig({
future: {
compatibilityVersion: 4
},
devtools: { enabled: true },
modules: [
'@nuxt/ui',
'@nuxt/content',
'@nuxt/image'
],
runtimeConfig: {
apiSecret: process.env.API_SECRET,
databaseUrl: process.env.DATABASE_URL,
public: {
apiBase: process.env.API_BASE || 'https://api.example.com',
appName: 'My App'
}
},
app: {
head: {
title: 'My Nuxt App',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
]
}
},
nitro: {
preset: 'cloudflare-pages'
},
typescript: {
strict: true,
typeCheck: true
}
})
Runtime Config Usage
const config = useRuntimeConfig()
const apiBase = config.public.apiBase
const apiSecret = config.apiSecret
Critical Rule: Always use useRuntimeConfig() instead of process.env for environment variables in production.
App Config vs Runtime Config
| Feature | App Config | Runtime Config |
|---|
| Location | app.config.ts | nuxt.config.ts |
| Hot reload | Yes | No |
| Secrets | No | Yes (server-only) |
| Use case | UI settings, themes | API keys, URLs |
export default defineAppConfig({
theme: {
primaryColor: '#3490dc'
},
ui: {
rounded: 'lg'
}
})
const appConfig = useAppConfig()
const color = appConfig.theme.primaryColor
Routing
File-Based Routing
app/pages/
├── index.vue → /
├── about.vue → /about
├── users/
│ ├── index.vue → /users
│ └── [id].vue → /users/:id
└── blog/
├── index.vue → /blog
├── [slug].vue → /blog/:slug
└── [...slug].vue → /blog/* (catch-all)
Dynamic Routes
<!-- app/pages/users/[id].vue -->
<script setup lang="ts">
const route = useRoute()
// Get route params
const userId = route.params.id
// Reactive (updates when route changes)
const userId = computed(() => route.params.id)
// Fetch user data
const { data: user } = await useFetch(`/api/users/${userId.value}`)
</script>
<template>
<div>
<h1>{{ user?.name }}</h1>
</div>
</template>
Navigation
<script setup>
const goToUser = (id: string) => {
navigateTo(`/users/${id}`)
}
const goBack = () => {
navigateTo(-1) // Go back in history
}
// With options
const goToLogin = () => {
navigateTo('/login', {
replace: true, // Replace current history entry
external: false // Internal navigation
})
}
</script>
<template>
<!-- Declarative navigation -->
<NuxtLink to="/about">About</NuxtLink>
<NuxtLink :to="`/users/${user.id}`">View User</NuxtLink>
<!-- Prefetching (default: on hover) -->
<NuxtLink to="/dashboard" prefetch>Dashboard</NuxtLink>
<!-- No prefetch -->
<NuxtLink to="/admin" :prefetch="false">Admin</NuxtLink>
</template>
Route Middleware
export default defineNuxtRouteMiddleware((to, from) => {
const { isAuthenticated } = useAuth()
if (!isAuthenticated.value) {
return navigateTo('/login')
}
})
<!-- app/pages/dashboard.vue -->
<script setup lang="ts">
definePageMeta({
middleware: 'auth'
})
</script>
Global Middleware
export default defineNuxtRouteMiddleware((to, from) => {
if (import.meta.client) {
window.gtag?.('event', 'page_view', {
page_path: to.path
})
}
})
Page Meta
<script setup lang="ts">
definePageMeta({
title: 'Dashboard',
middleware: ['auth'],
layout: 'admin',
pageTransition: { name: 'fade' },
keepalive: true
})
</script>
SEO & Meta Tags
useSeoMeta (Recommended)
<script setup lang="ts">
useSeoMeta({
title: 'My Page Title',
description: 'Page description for search engines',
ogTitle: 'My Page Title',
ogDescription: 'Page description',
ogImage: 'https://example.com/og-image.jpg',
ogUrl: 'https://example.com/my-page',
twitterCard: 'summary_large_image',
twitterTitle: 'My Page Title',
twitterDescription: 'Page description',
twitterImage: 'https://example.com/og-image.jpg'
})
</script>
useHead (More Control)
<script setup lang="ts">
useHead({
title: 'My Page Title',
meta: [
{ name: 'description', content: 'Page description' },
{ property: 'og:title', content: 'My Page Title' }
],
link: [
{ rel: 'canonical', href: 'https://example.com/my-page' }
],
script: [
{ src: 'https://example.com/script.js', defer: true }
]
})
</script>
Dynamic Meta Tags
<script setup lang="ts">
const { data: post } = await useFetch(`/api/posts/${route.params.slug}`)
useSeoMeta({
title: () => post.value?.title,
description: () => post.value?.excerpt,
ogImage: () => post.value?.image
})
</script>
Title Template
export default defineNuxtConfig({
app: {
head: {
titleTemplate: '%s | My App'
}
}
})
Error Handling
Error Page
<!-- app/error.vue -->
<script setup lang="ts">
import type { NuxtError } from '#app'
const props = defineProps<{
error: NuxtError
}>()
const handleError = () => {
clearError({ redirect: '/' })
}
</script>
<template>
<div class="error-page">
<h1>{{ error.statusCode }}</h1>
<p>{{ error.message }}</p>
<button @click="handleError">Go Home</button>
</div>
</template>
Error Boundaries (Component-Level)
<template>
<NuxtErrorBoundary @error="handleError">
<template #error="{ error, clearError }">
<div class="error-container">
<h2>Something went wrong</h2>
<p>{{ error.message }}</p>
<button @click="clearError">Try again</button>
</div>
</template>
<!-- Your component content -->
<MyComponent />
</NuxtErrorBoundary>
</template>
<script setup>
const handleError = (error: Error) => {
console.error('Component error:', error)
// Send to error tracking service
}
</script>
Throwing Errors
throw createError({
statusCode: 404,
statusMessage: 'Page Not Found',
fatal: true
})
throw createError({
statusCode: 400,
message: 'Invalid input'
})
API Error Handling
const { data, error } = await useFetch('/api/users')
if (error.value) {
showError({
statusCode: error.value.statusCode,
message: error.value.message
})
}
Common Anti-Patterns
Using process.env Instead of Runtime Config
const apiUrl = process.env.API_URL
const config = useRuntimeConfig()
const apiUrl = config.public.apiBase
Missing Middleware Guards
export default defineNuxtRouteMiddleware((to) => {
const { isAuthenticated } = useAuth()
if (!isAuthenticated.value) {
navigateTo('/login')
}
})
export default defineNuxtRouteMiddleware((to) => {
const { isAuthenticated } = useAuth()
if (!isAuthenticated.value) {
return navigateTo('/login')
}
})
Non-Reactive Route Params
const userId = route.params.id
const userId = computed(() => route.params.id)
Troubleshooting
Build Errors / Type Errors:
rm -rf .nuxt .output node_modules/.vite && bun install && bun run dev
Route Not Found:
- Check file is in
app/pages/ (not root pages/)
- Verify file extension is
.vue
- Check for typos in dynamic params
[id].vue
Middleware Not Running:
- Ensure file has
.global.ts suffix for global middleware
- Check
definePageMeta({ middleware: 'name' }) matches filename
- Verify middleware returns
navigateTo() or nothing
Meta Tags Not Updating:
- Use reactive values:
title: () => post.value?.title
- Ensure
useSeoMeta is called in <script setup>
Related Skills
- nuxt-data: Composables, data fetching, state management
- nuxt-server: Server routes, API patterns, database integration
- nuxt-production: Performance, testing, deployment
- nuxt-ui-v4: Nuxt UI component library
Templates Available
See templates/ directory for:
- Production-ready
nuxt.config.ts
app.vue with proper structure
- Middleware examples
Version: 4.0.0 | Last Updated: 2025-12-28 | License: MIT