Nuxt.js Debugging Guide
This guide provides a systematic approach to debugging Nuxt.js applications, covering SSR/SSG issues, Nitro server problems, hydration mismatches, composables, and more.
Common Error Patterns
- Hydration Mismatches
Hydration mismatches occur when the server-rendered HTML differs from what Vue expects on the client.
Symptoms:
-
Console warning: "Hydration text/node mismatch"
-
Content flickers or changes after page load
-
[Vue warn]: Hydration completed but contains mismatches
Common Causes:
// BAD: Using browser-only APIs during SSR const windowWidth = window.innerWidth // Errors on server
// GOOD: Guard with process.client or useNuxtApp() const windowWidth = ref(0) onMounted(() => { windowWidth.value = window.innerWidth })
// GOOD: Use ClientOnly component <ClientOnly> <BrowserOnlyComponent /> </ClientOnly>
// BAD: Date/time rendering inconsistency <span>{{ new Date().toLocaleString() }}</span> // Different on server vs client
// GOOD: Use consistent formatting or client-only <ClientOnly> <span>{{ formattedDate }}</span> <template #fallback>Loading...</template> </ClientOnly>
Debugging Steps:
-
Check browser console for specific mismatch details
-
Look for window , document , localStorage usage outside onMounted or process.client
-
Check for random values, dates, or user-specific data rendered during SSR
-
Use Vue DevTools to inspect component tree
- useFetch/useAsyncData Errors
Common Issues:
// ERROR: "useFetch is not defined" or composable called outside setup // BAD: Calling in regular function function fetchData() { const { data } = useFetch('/api/data') // Error! }
// GOOD: Call in setup or use $fetch in functions const { data, error, pending, refresh } = useFetch('/api/data')
// Or for functions: async function fetchData() { const data = await $fetch('/api/data') }
Key/Caching Issues:
// BAD: Same key returns cached data const { data: user1 } = useFetch('/api/user', { key: 'user' }) const { data: user2 } = useFetch('/api/user', { key: 'user' }) // Returns same cached data!
// GOOD: Use unique keys const { data: user1 } = useFetch('/api/user/1', { key: 'user-1' }) const { data: user2 } = useFetch('/api/user/2', { key: 'user-2' })
// Force refresh const { data, refresh } = useFetch('/api/data') await refresh() // Bypasses cache
Watch for Reactive Parameters:
// BAD: Non-reactive parameter won't trigger refetch
const userId = '123'
const { data } = useFetch(/api/user/${userId})
// GOOD: Use computed or getter for reactive URLs
const userId = ref('123')
const { data } = useFetch(() => /api/user/${userId.value})
// Or with watch const { data } = useFetch('/api/user', { query: { id: userId }, watch: [userId] })
- Nitro Server Errors
500 Internal Server Errors:
// Check server/api/ files for issues // server/api/example.ts
// BAD: Unhandled errors crash the endpoint export default defineEventHandler(async (event) => { const data = await fetchExternalAPI() // Unhandled rejection return data })
// GOOD: Proper error handling export default defineEventHandler(async (event) => { try { const data = await fetchExternalAPI() return data } catch (error) { throw createError({ statusCode: 500, statusMessage: 'Failed to fetch data', data: { originalError: error.message } }) } })
Reading Request Body:
// BAD: Wrong method to read body export default defineEventHandler(async (event) => { const body = event.body // undefined!
// GOOD: Use readBody const body = await readBody(event)
// For query params const query = getQuery(event)
// For route params const { id } = event.context.params })
- Plugin Initialization Issues
// plugins/my-plugin.ts
// BAD: Plugin errors break the entire app export default defineNuxtPlugin(() => { const api = new ExternalAPI() // May throw })
// GOOD: Error handling in plugins export default defineNuxtPlugin({ name: 'my-plugin', enforce: 'pre', // or 'post' async setup(nuxtApp) { try { const api = new ExternalAPI() return { provide: { api } } } catch (error) { console.error('Plugin initialization failed:', error) // Provide fallback or skip } } })
// Client-only plugin export default defineNuxtPlugin({ name: 'client-only-plugin', setup() { // This only runs on client } }) // Name file: plugins/my-plugin.client.ts
- Module Conflicts
Diagnosing Module Issues:
// nuxt.config.ts export default defineNuxtConfig({ modules: [ '@nuxtjs/tailwindcss', '@pinia/nuxt', // Module order can matter! ],
// Debug module loading debug: true, // Shows module loading in console })
Common Module Conflicts:
Clear module cache
rm -rf node_modules/.cache rm -rf .nuxt
Reinstall dependencies
rm -rf node_modules npm install
Debugging Tools
- Nuxt DevTools (Recommended)
// nuxt.config.ts export default defineNuxtConfig({ devtools: { enabled: true } })
Features:
-
Component inspector and tree
-
Pages and routing visualization
-
Composables state inspection
-
Server routes overview
-
Module dependencies
-
Payload inspection
-
Timeline for performance
Access: Press Shift + Alt + D or click floating icon in dev mode
- Sourcemaps Configuration
// nuxt.config.ts export default defineNuxtConfig({ sourcemap: { server: true, client: true } })
- VS Code Debugging
Create .vscode/launch.json :
{ "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Debug Nuxt Client", "url": "http://localhost:3000", "webRoot": "${workspaceFolder}" }, { "type": "node", "request": "launch", "name": "Debug Nuxt Server", "program": "${workspaceFolder}/node_modules/nuxi/bin/nuxi.mjs", "args": ["dev"], "cwd": "${workspaceFolder}" } ] }
- Node Inspector (Server-Side)
Start with debugger
nuxi dev --inspect
Or with specific host for Docker
nuxi dev --inspect=0.0.0.0
- Console Logging (Server vs Client)
// Runs on both server and client console.log('Universal log')
// Server-only logging if (process.server) { console.log('Server-side only') }
// Client-only logging if (process.client) { console.log('Client-side only') }
// In composables const nuxtApp = useNuxtApp() if (nuxtApp.ssrContext) { console.log('Server-side render') }
- Vue DevTools
Install Vue DevTools browser extension
Or use standalone
npx @vue/devtools
The Four Phases of Nuxt Debugging
Phase 1: Identify the Context
Determine where the error occurs:
// Check execution context console.log('Server:', process.server) console.log('Client:', process.client) console.log('Dev:', process.dev) console.log('SSR:', !!useNuxtApp().ssrContext)
Questions to answer:
-
Does it happen during SSR, hydration, or client navigation?
-
Is it a build-time or runtime error?
-
Does it only happen on certain routes?
-
Is it reproducible in development AND production?
Phase 2: Isolate the Component
<template> <div> <!-- Wrap suspect components --> <NuxtErrorBoundary @error="logError"> <SuspectComponent /> <template #error="{ error }"> <p>Error: {{ error.message }}</p> </template> </NuxtErrorBoundary> </div> </template>
<script setup> function logError(error) { console.error('Caught error:', error) } </script>
Phase 3: Check Data Flow
// Debug useFetch/useAsyncData const { data, error, pending, status } = useFetch('/api/data')
watch([data, error, pending], ([d, e, p]) => { console.log('Data:', d) console.log('Error:', e) console.log('Pending:', p) })
// Check payload (what's sent from server to client) const nuxtApp = useNuxtApp() console.log('Payload:', nuxtApp.payload)
Phase 4: Verify Build and Config
Type check
nuxi typecheck
Analyze bundle
nuxi analyze
Clean build
rm -rf .nuxt .output node_modules/.cache nuxi build
Quick Reference Commands
Development
Start dev server
nuxi dev
Start with debugging
nuxi dev --inspect
Start on specific port
nuxi dev --port 3001
Start with HTTPS
nuxi dev --https
Building and Analysis
Production build
nuxi build
Generate static site
nuxi generate
Preview production build
nuxi preview
Analyze bundle size
nuxi analyze
Type checking
nuxi typecheck
Prepare Nuxt (generate types)
nuxi prepare
Maintenance
Clean Nuxt files
nuxi cleanup
Upgrade Nuxt
nuxi upgrade
Add module
nuxi module add @nuxtjs/tailwindcss
Create new component/page/etc
nuxi add component MyComponent nuxi add page about nuxi add composable useMyComposable nuxi add api hello
Error Handling Patterns
Global Error Handler
// plugins/error-handler.ts export default defineNuxtPlugin((nuxtApp) => { nuxtApp.vueApp.config.errorHandler = (error, instance, info) => { console.error('Vue Error:', error) console.error('Component:', instance) console.error('Info:', info) }
nuxtApp.hook('vue:error', (error, instance, info) => { console.error('Nuxt Vue Error Hook:', error) })
nuxtApp.hook('app:error', (error) => { console.error('App Error:', error) }) })
Custom Error Page
<!-- error.vue (in root, NOT in pages/) --> <template> <div class="error-page"> <h1>{{ error.statusCode }}</h1> <p>{{ error.message }}</p> <button @click="handleError">Go Home</button> </div> </template>
<script setup> const props = defineProps({ error: Object })
const handleError = () => clearError({ redirect: '/' }) </script>
Programmatic Error Handling
// Throw errors throw createError({ statusCode: 404, statusMessage: 'Page not found', fatal: true // Triggers error page })
// Show error without navigation showError({ statusCode: 500, statusMessage: 'Something went wrong' })
// Clear error clearError({ redirect: '/' })
// Access current error const error = useError()
Error Boundary for Components
<template> <NuxtErrorBoundary> <RiskyComponent />
<template #error="{ error, clearError }">
<div class="error-box">
<p>Component failed: {{ error.message }}</p>
<button @click="clearError">Retry</button>
</div>
</template>
</NuxtErrorBoundary> </template>
SSR-Specific Debugging
Payload Issues
// Debug what's in the payload const nuxtApp = useNuxtApp() onMounted(() => { console.log('SSR Payload:', nuxtApp.payload) console.log('SSR Data:', nuxtApp.payload.data) console.log('SSR State:', nuxtApp.payload.state) })
Async Data Not Available
// Ensure data is awaited properly const { data } = await useFetch('/api/data')
// For lazy loading (doesn't block navigation) const { data, pending } = useLazyFetch('/api/data')
// Watch for data availability watch(data, (newData) => { if (newData) { console.log('Data loaded:', newData) } })
Server-Only Code Leaking to Client
// Use server utilities correctly // server/utils/db.ts - only available in server/
// For runtime config (secrets) // nuxt.config.ts export default defineNuxtConfig({ runtimeConfig: { apiSecret: '', // Server-only public: { apiBase: '' // Exposed to client } } })
// Usage const config = useRuntimeConfig() // config.apiSecret - only on server // config.public.apiBase - available everywhere
Performance Debugging
Identify Slow Components
// nuxt.config.ts export default defineNuxtConfig({ experimental: { componentIslands: true // For heavy components } })
<!-- Use islands for heavy server components --> <NuxtIsland name="HeavyChart" :props="{ data: chartData }" />
Lazy Loading
// Lazy load components const HeavyComponent = defineAsyncComponent(() => import('~/components/HeavyComponent.vue') )
// Or use Nuxt's auto-import with Lazy prefix <template> <LazyHeavyComponent v-if="showHeavy" /> </template>
Bundle Analysis
Generate bundle analysis
nuxi analyze
Check what's in your bundle
cat .output/public/_nuxt/builds/meta/*.json | jq
Common Gotchas
- Composables Must Be Called in Setup
// BAD function handleClick() { const route = useRoute() // Error! }
// GOOD const route = useRoute() function handleClick() { console.log(route.path) }
- Reactive Data in useFetch
// BAD: Non-reactive
const id = '123'
useFetch(/api/items/${id})
// GOOD: Reactive
const id = ref('123')
useFetch(() => /api/items/${id.value})
- Navigate vs Router
// Prefer navigateTo over useRouter for navigation await navigateTo('/dashboard') await navigateTo({ path: '/user', query: { id: 1 } })
// For programmatic redirects in server export default defineEventHandler((event) => { return sendRedirect(event, '/login', 302) })
- Middleware Execution Order
// Named middleware runs in alphabetical order // middleware/01.auth.global.ts runs before middleware/02.analytics.global.ts
// Route-specific middleware definePageMeta({ middleware: ['auth', 'premium'] // Runs in order })
- State Pollution in SSR
// BAD: Shared state between requests const globalState = reactive({})
// GOOD: Use useState for SSR-safe state const state = useState('key', () => ({}))
// Or Pinia with proper SSR setup const store = useMyStore()
Resources
-
Nuxt Documentation - Debugging
-
Nuxt DevTools
-
Nuxt Error Handling
-
Vue DevTools
-
Nuxt GitHub Discussions