Nuxt Upgrade Guide
Compare Nuxt versions and see exactly what changes, what requires action, and what you can start using.
→
12Changes
10Breaking
12Actions
0Features
2Migration tools
Breaking Changes
Nuxt 3 rewrites the configuration and build system. CommonJS configuration and legacy buildModules need migration.
module.exports = {
buildModules: ['@nuxtjs/tailwindcss']
}
export default defineNuxtConfig({
modules: ['@nuxtjs/tailwindcss']
})
Migration Convert the config to ESM. Move compatible modules to modules and verify each module supports Nuxt 3.
Nuxt 3 uses Vue 3 and Vue Router 4. Vue 2 plugins, filters and removed APIs need review; the Options API remains supported.
<template>{{ price | currency }}</template>
<script>
export default { filters: { currency: v => '$' + v } }
</script>
<script setup>
const props = defineProps({ price: Number })
const formatted = computed(() => '$' + props.price)
</script>
<template>{{ formatted }}</template>
Migration Update Vue dependencies together and replace filters with methods or computed values. Check third-party Vue compatibility.
useAsyncData and useFetch transfer server-fetched data through the Nuxt payload. A direct $fetch in component setup can fetch again during hydration.
<script>
export default {
async asyncData({ $axios }) {
return { posts: await $axios.$get('/api/posts') }
}
}
</script>
<script setup>
const { data: posts, error, status } = await useFetch('/api/posts')
</script>
Migration Use useFetch for URLs or useAsyncData for custom handlers. Return serializable data and handle pending/error states.
The context/inject plugin signature is replaced by a Nuxt app plugin. Plugins in plugins/ are registered automatically.
export default (context, inject) => {
inject('hello', name => 'Hello ' + name)
}
// plugins/hello.ts
export default defineNuxtPlugin(() => ({
provide: { hello: (name: string) => 'Hello ' + name }
}))
// In component setup:
const { $hello } = useNuxtApp()
Migration Return helpers through provide and use useNuxtApp to consume them. Name browser-only plugins with .client.ts.
defineNuxtRouteMiddleware receives to/from rather than Nuxt 2 context. Redirects and aborts must be returned.
export default function ({ store, redirect }) {
if (!store.state.user) return redirect('/login')
}
export default defineNuxtRouteMiddleware(() => {
const user = useState('user', () => null)
if (!user.value) return navigateTo('/login')
})
Migration Move middleware to the new signature, return navigateTo/abortNavigation and use .global.ts for global route middleware.
Nuxt 2 underscore route conventions change to Vue Router 4-style bracket filenames.
pages/users/_id.vue
pages/_.vue
pages/users/[id].vue
pages/[...slug].vue
Migration Rename dynamic/catch-all files and verify optional parameters, generated routes and direct SSR requests.
The Nuxt 2 Nuxt and NuxtChild components are replaced. Layout and page settings move to definePageMeta.
<!-- layouts/default.vue -->
<template><main><Nuxt /></main></template>
<!-- layouts/default.vue -->
<template><main><slot /></main></template>
<!-- app.vue -->
<template><NuxtLayout><NuxtPage /></NuxtLayout></template>
Migration Use slot in layouts and NuxtPage for pages/nested pages. Wrap the root page in NuxtLayout when using layouts.
publicRuntimeConfig/privateRuntimeConfig become runtimeConfig with a public section. Public values are exposed to the browser.
export default {
privateRuntimeConfig: { apiSecret: '' },
publicRuntimeConfig: { apiBase: '/api' }
}
export default defineNuxtConfig({
runtimeConfig: {
apiSecret: '', // NUXT_API_SECRET; server only
public: { apiBase: '/api' } // NUXT_PUBLIC_API_BASE
}
})
Migration Keep secrets at the root and read configuration using useRuntimeConfig. Use matching NUXT_ environment variables at runtime.
Nuxt 3 uses the standalone Nitro server with file-based API routes and H3 handlers.
// nuxt.config.js
export default { serverMiddleware: [
{ path: '/api/health', handler: '~/api/health.js' }
] }
// api/health.js
export default (req, res) => res.end('ok')
// server/api/health.get.ts
export default defineEventHandler(() => ({ status: 'ok' }))
Migration Move Express/connect middleware to compatible Nitro handlers. Preserve authentication, status codes and response semantics.
Node deployments run .output/server/index.mjs. Nuxt 2 nuxt start and .nuxt directory deployment instructions do not match Nitro.
npm run build
npx nuxt start
npm run build
node .output/server/index.mjs
Migration Build for the intended Nitro preset and ship the complete .output. Check SSR pages, environment values, static assets and health endpoints.
Migration Tools
Nuxt 3 uses Unhead. useHead/useSeoMeta support reactive SSR metadata; duplicate-tag identifiers such as hid are unnecessary.
<script>
export default {
head: () => ({ meta: [{ hid: 'description', name: 'description', content: 'Upgrade guide' }] })
}
</script>
<script setup>
useSeoMeta({ title: 'Upgrade guide', description: 'Upgrade guide' })
useHead({ link: [{ rel: 'canonical', href: 'https://example.com/guide' }] })
</script>
Migration Migrate page titles, descriptions and canonical links, then inspect server HTML. Do not expose server-only values in metadata.
Nuxt 3 has no built-in Vuex integration. useState creates shared SSR-safe state; module-scope refs can leak state between server requests.
// Shared across requests when imported by server code:
export const user = ref(null)
// Call from component setup or a Nuxt composable:
export const useUser = () => useState('user', () => null)
Migration Migrate small state to useState or install compatible Pinia integration. Keep user-specific state inside Nuxt request context.