nuxt logo

Traducción de Documentación (No Oficial)

Guía de Actualización

Aprende cómo actualizar a la última versión de Nuxt.

Actualizando Nuxt

Última versión

Para actualizar Nuxt a la última versión, utiliza el comando nuxt upgrade.

npx nuxt upgrade

Canal de Lanzamiento Nocturno

Para usar la última compilación de Nuxt y probar características antes de su lanzamiento, lee la guía sobre el canal de lanzamiento nocturno.

La etiqueta latest del canal de lanzamiento nocturno actualmente sigue la rama Nuxt v4, lo que significa que es particularmente probable que tenga cambios importantes en este momento — ¡ten cuidado! Puedes optar por las versiones nocturnas de la rama 3.x con "nuxt": "npm:nuxt-nightly@3x".

Probando Nuxt 4

Nuxt 4 está programado para lanzarse en el segundo trimestre de 2025. Incluirá todas las características actualmente disponibles a través de compatibilityVersion: 4.

Hasta el lanzamiento, es posible probar muchos de los cambios importantes de Nuxt 4 desde la versión 3.12+ de Nuxt.

Optando por Nuxt 4

Primero, actualiza Nuxt a la última versión.

Luego puedes configurar tu compatibilityVersion para que coincida con el comportamiento de Nuxt 4:

nuxt.config.ts
export default defineNuxtConfig({
  future: {
    compatibilityVersion: 4,
  },
  // Para volver a habilitar _todo_ el comportamiento de Nuxt v3, configura las siguientes opciones:
  // srcDir: '.',
  // dir: {
  //   app: 'app'
  // },
  // experimental: {
  //   scanPageMeta: 'after-resolve',
  //   sharedPrerenderData: false,
  //   compileTemplate: true,
  //   resetAsyncDataToUndefined: true,
  //   templateUtils: true,
  //   relativeWatchPaths: true,
  //   normalizeComponentNames: false,
  //   spaLoadingTemplateLocation: 'within',
  //   parseErrorData: false,
  //   pendingWhenIdle: true,
  //   alwaysRunFetchOnKeyChange: true,
  //   defaults: {
  //     useAsyncData: {
  //       deep: true
  //     }
  //   }
  // },
  // features: {
  //   inlineStyles: true
  // },
  // unhead: {
  //   renderSSRHeadOptions: {
  //     omitLineBreaks: false
  //   }
  // }
})

Por ahora, necesitas definir la versión de compatibilidad en cada capa que opte por el comportamiento de Nuxt 4. Esto no será necesario después del lanzamiento de Nuxt 4.

Cuando configuras tu compatibilityVersion a 4, los valores predeterminados en toda tu configuración de Nuxt cambiarán para optar por el comportamiento de Nuxt v4, pero puedes volver a habilitar granularmente el comportamiento de Nuxt v3 al probar, siguiendo las líneas comentadas arriba. Por favor, presenta problemas si es así, para que podamos abordarlos en Nuxt o en el ecosistema.

Los cambios importantes o significativos se anotarán aquí junto con los pasos de migración para la compatibilidad hacia atrás/adelante.

Esta sección está sujeta a cambios hasta el lanzamiento final, así que por favor revisa aquí regularmente si estás probando Nuxt 4 usando compatibilityVersion: 4.

Migrando Usando Codemods

Para facilitar el proceso de actualización, hemos colaborado con el equipo de Codemod para automatizar muchos pasos de migración con algunos codemods de código abierto.

Si encuentras algún problema, por favor repórtalo al equipo de Codemod con npx codemod feedback 🙏

Para una lista completa de codemods de Nuxt 4, información detallada sobre cada uno, su fuente y varias formas de ejecutarlos, visita el Registro de Codemod.

Puedes ejecutar todos los codemods mencionados en esta guía usando la siguiente receta de codemod:

npx codemod@latest nuxt/4/migration-recipe

Este comando ejecutará todos los codemods en secuencia, con la opción de deseleccionar cualquiera que no desees ejecutar. Cada codemod también se enumera a continuación junto con su respectivo cambio y puede ejecutarse de forma independiente.

Nueva Estructura de Directorios

🚦 Nivel de Impacto: Significativo

Nuxt ahora tiene por defecto una nueva estructura de directorios, con compatibilidad hacia atrás (por lo que si Nuxt detecta que estás usando la estructura antigua, como con un directorio pages/ de nivel superior, esta nueva estructura no se aplicará).

👉 Ver RFC completo

Qué Cambió

  • el nuevo srcDir predeterminado de Nuxt es app/ por defecto, y la mayoría de las cosas se resuelven desde allí.
  • serverDir ahora tiene por defecto <rootDir>/server en lugar de <srcDir>/server
  • layers/, modules/ y public/ se resuelven de forma relativa a <rootDir> por defecto
  • si usas Nuxt Content v2.13+, content/ se resuelve de forma relativa a <rootDir>
  • se añade un nuevo dir.app, que es el directorio donde buscamos router.options.ts y spa-loading-template.html - esto tiene por defecto <srcDir>/
Un ejemplo de estructura de carpetas v4.
.output/
.nuxt/
app/
  assets/
  components/
  composables/
  layouts/
  middleware/
  pages/
  plugins/
  utils/
  app.config.ts
  app.vue
  router.options.ts
content/
layers/
modules/
node_modules/
public/
server/
  api/
  middleware/
  plugins/
  routes/
  utils/
nuxt.config.ts

👉 Para más detalles, consulta el PR que implementa este cambio.

Razones para el Cambio

  1. Rendimiento - colocar todo tu código en la raíz de tu repositorio causa problemas con las carpetas .git/ y node_modules/ siendo escaneadas/incluidas por los observadores de FS, lo que puede retrasar significativamente el inicio en sistemas que no son Mac OS.
  2. Seguridad de tipo en IDE - server/ y el resto de tu aplicación se ejecutan en dos contextos completamente diferentes con diferentes importaciones globales disponibles, y asegurarse de que server/ no esté dentro de la misma carpeta que el resto de tu aplicación es un gran primer paso para garantizar que obtengas buenas autocompletaciones en tu IDE.

Pasos de Migración

  1. Crea un nuevo directorio llamado app/.
  2. Mueve tus carpetas assets/, components/, composables/, layouts/, middleware/, pages/, plugins/ y utils/ debajo de él, así como app.vue, error.vue, app.config.ts. Si tienes un app/router-options.ts o app/spa-loading-template.html, estas rutas permanecen igual.
  3. Asegúrate de que tus carpetas nuxt.config.ts, content/, layers/, modules/, public/ y server/ permanezcan fuera de la carpeta app/, en la raíz de tu proyecto.
  4. Recuerda actualizar cualquier archivo de configuración de terceros para trabajar con la nueva estructura de directorios, como tu configuración de tailwindcss o eslint (si es necesario - @nuxtjs/tailwindcss debería configurar automáticamente tailwindcss correctamente).

Puedes automatizar esta migración ejecutando npx codemod@latest nuxt/4/file-structure

Sin embargo, la migración no es necesaria. Si deseas mantener tu estructura de carpetas actual, Nuxt debería detectarla automáticamente. (Si no lo hace, por favor presenta un problema.) La única excepción es que si ya tienes un srcDir personalizado. En este caso, debes tener en cuenta que tus carpetas modules/, public/ y server/ se resolverán desde tu rootDir en lugar de desde tu srcDir personalizado. Puedes anular esto configurando dir.modules, dir.public y serverDir si lo necesitas.

También puedes forzar una estructura de carpetas v3 con la siguiente configuración:

nuxt.config.ts
export default defineNuxtConfig({
  // Esto revierte el nuevo valor predeterminado de srcDir de `app` a tu directorio raíz
  srcDir: '.',
  // Esto especifica el prefijo de directorio para `app/router.options.ts` y `app/spa-loading-template.html`
  dir: {
    app: 'app'
  }
})

Capa de Obtención de Datos Singleton

🚦 Nivel de Impacto: Moderado

Qué Cambió

El sistema de obtención de datos de Nuxt (useAsyncData y useFetch) ha sido reorganizado significativamente para mejorar el rendimiento y la consistencia:

  1. Referencias compartidas para la misma clave: Todas las llamadas a useAsyncData o useFetch con la misma clave ahora comparten las mismas referencias data, error y status. Esto significa que es importante que todas las llamadas con una clave explícita no tengan opciones deep, transform, pick, getCachedData o default en conflicto.

  2. Más control sobre getCachedData: La función getCachedData ahora se llama cada vez que se obtienen datos, incluso si esto es causado por un observador o al llamar a refreshNuxtData. (Anteriormente, siempre se obtenían nuevos datos y esta función no se llamaba en estos casos.) Para permitir más control sobre cuándo usar datos en caché y cuándo volver a obtenerlos, la función ahora recibe un objeto de contexto con la causa de la solicitud.

  3. Soporte para claves reactivas: Ahora puedes usar referencias computadas, referencias simples o funciones getter como claves, lo que permite la obtención automática de datos (y almacena los datos por separado).

  4. Limpieza de datos: Cuando se desmonta el último componente que usa datos obtenidos con useAsyncData, Nuxt eliminará esos datos para evitar un uso de memoria en constante crecimiento.

Razones para el Cambio

Estos cambios se han realizado para mejorar el uso de memoria y aumentar la consistencia con los estados de carga a través de las llamadas de useAsyncData.

Pasos de Migración

  1. Verifica opciones inconsistentes: Revisa cualquier componente que use la misma clave con diferentes opciones o funciones de obtención.

    // Esto ahora activará una advertencia
    const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { deep: false })
    const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { deep: true })
    

    Puede ser beneficioso extraer cualquier llamada a useAsyncData que comparta una clave explícita (y tenga opciones personalizadas) en su propio composable:

    composables/useUserData.ts
    export function useUserData(userId: string) {
      return useAsyncData(
        `user-${userId}`,
        () => fetchUser(userId),
        { 
          deep: true,
          transform: (user) => ({ ...user, lastAccessed: new Date() })
        }
      )
    }
    
  2. Actualiza las implementaciones de getCachedData:

    useAsyncData('key', fetchFunction, {
    -  getCachedData: (key, nuxtApp) => {
    -    return cachedData[key]
    -  }
    +  getCachedData: (key, nuxtApp, ctx) => {
    +    // ctx.cause - puede ser 'initial' | 'refresh:hook' | 'refresh:manual' | 'watch'
    +    
    +    // Ejemplo: No usar caché en actualización manual
    +    if (ctx.cause === 'refresh:manual') return undefined
    +    
    +    return cachedData[key]
    +  }
    })
    

Alternativamente, por ahora, puedes desactivar este comportamiento con:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    granularCachedData: false,
    purgeCachedData: false
  }
})

Deducción de Metadatos de Ruta

🚦 Nivel de Impacto: Mínimo

Qué Cambió

Es posible establecer algunos metadatos de ruta usando definePageMeta, como el name, path, etc. Anteriormente, estos estaban disponibles tanto en la ruta como en los metadatos de la ruta (por ejemplo, route.name y route.meta.name).

Ahora, solo son accesibles en el objeto de ruta.

Razones para el Cambio

Esto es el resultado de habilitar experimental.scanPageMeta por defecto, y es una optimización de rendimiento.

Pasos de Migración

La migración debería ser sencilla:

  const route = useRoute()
  
- console.log(route.meta.name)
+ console.log(route.name)

Nombres de Componentes Normalizados

🚦 Nivel de Impacto: Moderado

Vue ahora generará nombres de componentes que coinciden con el patrón de nomenclatura de componentes de Nuxt.

Qué Cambió

Por defecto, si no lo has configurado manualmente, Vue asignará un nombre de componente que coincida con el nombre del archivo del componente.

Estructura de directorios
├─ components/
├─── SomeFolder/
├───── MyComponent.vue

En este caso, el nombre del componente sería MyComponent, en lo que respecta a Vue. Si quisieras usar <KeepAlive> con él, o identificarlo en las DevTools de Vue, necesitarías usar este nombre.

Pero para auto-importarlo, necesitarías usar SomeFolderMyComponent.

Con este cambio, estos dos valores coincidirán, y Vue generará un nombre de componente que coincida con el patrón de nomenclatura de componentes de Nuxt.

Pasos de Migración

Asegúrate de usar el nombre actualizado en cualquier prueba que use findComponent de @vue/test-utils y en cualquier <KeepAlive> que dependa del nombre de tu componente.

Alternativamente, por ahora, puedes desactivar este comportamiento con:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    normalizeComponentNames: false
  }
})

Unhead v2

🚦 Nivel de Impacto: Mínimo

Qué Cambió

Unhead, utilizado para generar etiquetas <head>, se ha actualizado a la versión 2. Aunque es mayormente compatible, incluye varios cambios importantes para las API de bajo nivel.

  • Props eliminadas: vmid, hid, children, body.
  • La entrada de Promise ya no es compatible.
  • Las etiquetas ahora se ordenan usando Capo.js por defecto.

Pasos de Migración

Los cambios anteriores deberían tener un impacto mínimo en tu aplicación.

Si tienes problemas, deberías verificar:

  • No estás usando ninguna de las props eliminadas.
useHead({
  meta: [{ 
    name: 'description', 
    // las etiquetas meta no necesitan un vmid, o una clave    
-   vmid: 'description' 
-   hid: 'description'
  }]
})
import { TemplateParamsPlugin, AliasSortingPlugin } from '@unhead/vue/plugins'

export default defineNuxtPlugin({
  setup() {
    const unhead = injectHead()
    unhead.use(TemplateParamsPlugin)
    unhead.use(AliasSortingPlugin)
  }
})

Aunque no es necesario, se recomienda actualizar cualquier importación de @unhead/vue a #imports o nuxt/app.

-import { useHead } from '@unhead/vue'
+import { useHead } from '#imports'

Si aún tienes problemas, puedes volver al comportamiento de la v1 habilitando la configuración head.legacy.

export default defineNuxtConfig({
  unhead: {
    legacy: true,
  }
})

Nueva Ubicación del DOM para la Pantalla de Carga de SPA

🚦 Nivel de Impacto: Mínimo

Qué Cambió

Al renderizar una página solo para cliente (con ssr: false), opcionalmente renderizamos una pantalla de carga (desde app/spa-loading-template.html), dentro de la raíz de la aplicación Nuxt:

<div id="__nuxt">
  <!-- plantilla de carga de spa -->
</div>

Ahora, por defecto, renderizamos la plantilla junto a la raíz de la aplicación Nuxt:

<div id="__nuxt"></div>
<!-- plantilla de carga de spa -->

Razones para el Cambio

Esto permite que la plantilla de carga de spa permanezca en el DOM hasta que se resuelva la suspensión de la aplicación Vue, evitando un destello de blanco.

Pasos de Migración

Si estabas apuntando a la plantilla de carga de spa con CSS o document.queryElement, necesitarás actualizar tus selectores. Para este propósito, puedes usar las nuevas opciones de configuración app.spaLoaderTag y app.spaLoaderAttrs.

Alternativamente, puedes volver al comportamiento anterior con:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    spaLoadingTemplateLocation: 'within',
  }
})

Datos de error.data Analizados

🚦 Nivel de Impacto: Mínimo

Era posible lanzar un error con una propiedad data, pero esta no se analizaba. Ahora, se analiza y está disponible en el objeto error. Aunque es una corrección, esto es técnicamente un cambio importante si dependías del comportamiento anterior y lo analizabas manualmente.

Pasos de Migración

Actualiza tu error.vue personalizado para eliminar cualquier análisis adicional de error.data:

  <script setup lang="ts">
  import type { NuxtError } from '#app'

  const props = defineProps({
    error: Object as () => NuxtError
  })

- const data = JSON.parse(error.data)
+ const data = error.data
  </script>

Alternativamente, puedes desactivar este cambio:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    parseErrorData: false
  },
})

Estilos en Línea Más Granulares

🚦 Nivel de Impacto: Moderado

Nuxt ahora solo incluirá estilos en línea para componentes Vue, no CSS global.

Qué Cambió

Anteriormente, Nuxt incluía todo el CSS, incluidos los estilos globales, y eliminaba los elementos <link> a archivos CSS separados. Ahora, Nuxt solo hará esto para componentes Vue (que anteriormente producían fragmentos separados de CSS). Creemos que este es un mejor equilibrio para reducir las solicitudes de red separadas (como antes, no habrá solicitudes separadas para archivos .css individuales por página o por componente en la carga inicial), así como permitir el almacenamiento en caché de un solo archivo CSS global y reducir el tamaño de descarga del documento de la solicitud inicial.

Pasos de Migración

Esta característica es completamente configurable y puedes volver al comportamiento anterior configurando inlineStyles: true para incluir CSS global así como CSS por componente.

nuxt.config.ts
export default defineNuxtConfig({
  features: {
    inlineStyles: true
  }
})

Escaneo de Metadatos de Página Después de la Resolución

🚦 Nivel de Impacto: Mínimo

Qué Cambió

Ahora escaneamos los metadatos de la página (definidos en definePageMeta) después de llamar al hook pages:extend en lugar de antes.

Razones para el Cambio

Esto fue para permitir el escaneo de metadatos para páginas que los usuarios querían agregar en pages:extend. Todavía ofrecemos una oportunidad para cambiar o anular los metadatos de la página en un nuevo hook pages:resolved.

Pasos de Migración

Si deseas anular los metadatos de la página, hazlo en pages:resolved en lugar de en pages:extend.

  export default defineNuxtConfig({
    hooks: {
-     'pages:extend'(pages) {
+     'pages:resolved'(pages) {
        const myPage = pages.find(page => page.path === '/')
        myPage.meta ||= {}
        myPage.meta.layout = 'overridden-layout'
      }
    }
  })

Alternativamente, puedes volver al comportamiento anterior con:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    scanPageMeta: true
  }
})

Datos de Prerender Compartidos

🚦 Nivel de Impacto: Medio

Qué Cambió

Habilitamos una característica experimental previamente para compartir datos de llamadas useAsyncData y useFetch, a través de diferentes páginas. Ver PR original.

Razones para el Cambio

Esta característica comparte automáticamente los datos de carga entre páginas que son prerenderizadas. Esto puede resultar en una mejora significativa del rendimiento al prerenderizar sitios que usan useAsyncData o useFetch y obtienen los mismos datos en diferentes páginas.

Por ejemplo, si tu sitio requiere una llamada useFetch para cada página (por ejemplo, para obtener datos de navegación para un menú, o configuraciones del sitio desde un CMS), estos datos solo se obtendrían una vez al prerenderizar la primera página que los usa, y luego se almacenarían en caché para su uso al prerenderizar otras páginas.

Pasos de Migración

Asegúrate de que cualquier clave única de tus datos siempre sea resoluble a los mismos datos. Por ejemplo, si estás usando useAsyncData para obtener datos relacionados con una página en particular, deberías proporcionar una clave que coincida de manera única con esos datos. (useFetch debería hacer esto automáticamente por ti.)

app/pages/test/[slug\
// Esto sería inseguro en una página dinámica (por ejemplo, `[slug].vue`) porque el slug de la ruta hace una diferencia
// en los datos obtenidos, pero Nuxt no puede saberlo porque no está reflejado en la clave.
const route = useRoute()
const { data } = await useAsyncData(async () => {
  return await $fetch(`/api/my-page/${route.params.slug}`)
})
// En su lugar, deberías usar una clave que identifique de manera única los datos obtenidos.
const { data } = await useAsyncData(route.params.slug, async () => {
  return await $fetch(`/api/my-page/${route.params.slug}`)
})

Alternativamente, puedes desactivar esta característica con:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    sharedPrerenderData: false
  }
})

Valores Predeterminados de data y error en useAsyncData y useFetch

🚦 Nivel de Impacto: Mínimo

Qué Cambió

Los objetos data y error devueltos de useAsyncData ahora tendrán por defecto undefined.

Razones para el Cambio

Anteriormente, data se inicializaba en null pero se restablecía en clearNuxtData a undefined. error se inicializaba en null. Este cambio es para lograr una mayor consistencia.

Pasos de Migración

Si estabas verificando si data.value o error.value eran null, puedes actualizar estas verificaciones para verificar undefined en su lugar.

Puedes automatizar este paso ejecutando npx codemod@latest nuxt/4/default-data-error-value

Si encuentras algún problema, puedes volver al comportamiento anterior con:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    defaults: {
      useAsyncData: {
        value: 'null',
        errorValue: 'null'
      }
    }
  }
})

Por favor, presenta un problema si estás haciendo esto, ya que no planeamos mantener esto como configurable.

Eliminación de valores boolean obsoletos para la opción dedupe al llamar a refresh en useAsyncData y useFetch

🚦 Nivel de Impacto: Mínimo

Qué Cambió

Anteriormente era posible pasar dedupe: boolean a refresh. Estos eran alias de cancel (true) y defer (false).

app.vue
const { refresh } = await useAsyncData(async () => ({ message: 'Hello, Nuxt!' }))

async function refreshData () {
  await refresh({ dedupe: true })
}

Razones para el Cambio

Estos alias fueron eliminados, para mayor claridad.

El problema surgió al agregar dedupe como una opción a useAsyncData, y eliminamos los valores booleanos ya que terminaron siendo opuestos.

refresh({ dedupe: false }) significaba no cancelar solicitudes existentes a favor de esta nueva. Pero pasar dedupe: true dentro de las opciones de useAsyncData significa no hacer nuevas solicitudes si hay una solicitud pendiente existente. (Ver PR.)

Pasos de Migración

La migración debería ser sencilla:

  const { refresh } = await useAsyncData(async () => ({ message: 'Hello, Nuxt 3!' }))
  
  async function refreshData () {
-   await refresh({ dedupe: true })
+   await refresh({ dedupe: 'cancel' })

-   await refresh({ dedupe: false })
+   await refresh({ dedupe: 'defer' })
  }

Puedes automatizar este paso ejecutando npx codemod@latest nuxt/4/deprecated-dedupe-value

Respeto a los valores predeterminados al borrar data en useAsyncData y useFetch

🚦 Nivel de Impacto: Mínimo

Qué Cambió

Si proporcionas un valor default personalizado para useAsyncData, ahora se usará al llamar a clear o clearNuxtData y se restablecerá a su valor predeterminado en lugar de simplemente desasignarse.

Razones para el Cambio

A menudo, los usuarios establecen un valor vacío apropiado, como un array vacío, para evitar la necesidad de verificar null/undefined al iterar sobre él. Esto debería respetarse al restablecer/borrar los datos.

Pasos de Migración

Si encuentras algún problema, puedes volver al comportamiento anterior, por ahora, con:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    resetAsyncDataToUndefined: true,
  }
})

Por favor, presenta un problema si estás haciendo esto, ya que no planeamos mantener esto como configurable.

Alineación del valor pending en useAsyncData y useFetch

🚦 Nivel de Impacto: Medio

El objeto pending devuelto de useAsyncData, useFetch, useLazyAsyncData y useLazyFetch ahora es una propiedad computada que es true solo cuando status también está pendiente.

Qué Cambió

Ahora, cuando se pasa immediate: false, pending será false hasta que se realice la primera solicitud. Esto es un cambio del comportamiento anterior, donde pending siempre era true hasta que se realizaba la primera solicitud.

Razones para el Cambio

Esto alinea el significado de pending con la propiedad status, que también está pendiente cuando la solicitud está en progreso.

Pasos de Migración

Si dependes de la propiedad pending, asegúrate de que tu lógica tenga en cuenta el nuevo comportamiento donde pending solo será true cuando el estado también esté pendiente.

  <template>
-   <div v-if="!pending">
+   <div v-if="status === 'success'">
      <p>Data: {{ data }}</p>
    </div>
    <div v-else>
      <p>Loading...</p>
    </div>
  </template>
  <script setup lang="ts">
  const { data, pending, execute, status } = await useAsyncData(() => fetch('/api/data'), {
    immediate: false
  })
  onMounted(() => execute())
  </script>

Alternativamente, puedes revertir temporalmente al comportamiento anterior con:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    pendingWhenIdle: true
  }
})

Comportamiento de Cambio de Clave en useAsyncData y useFetch

🚦 Nivel de Impacto: Medio

Qué Cambió

Al usar claves reactivas en useAsyncData o useFetch, Nuxt vuelve a obtener datos automáticamente cuando cambia la clave. Cuando se establece immediate: false, useAsyncData solo obtendrá datos cuando cambie la clave si los datos ya se han obtenido una vez.

Anteriormente, useFetch tenía un comportamiento ligeramente diferente. Siempre obtenía datos cuando cambiaba la clave.

Ahora, useFetch y useAsyncData se comportan de manera consistente, solo obteniendo datos cuando cambia la clave si los datos ya se han obtenido una vez.

Razones para el Cambio

Esto asegura un comportamiento consistente entre useAsyncData y useFetch, y previene obtenciones inesperadas. Si has configurado immediate: false, entonces debes llamar a refresh o execute o los datos nunca se obtendrán en useFetch o useAsyncData.

Pasos de Migración

Este cambio debería mejorar generalmente el comportamiento esperado, pero si esperabas que cambiar la clave o las opciones de un useFetch no inmediato, ahora necesitarás activarlo manualmente la primera vez.

  const id = ref('123')
  const { data, execute } = await useFetch('/api/test', {
    query: { id },
    immediate: false
  )
+ watch(id, execute, { once: true })

Para optar por no participar en este comportamiento:

// O globalmente en tu configuración de Nuxt
export default defineNuxtConfig({
  experimental: {
    alwaysRunFetchOnKeyChange: true
  }
})

Reactividad de Datos Superficial en useAsyncData y useFetch

🚦 Nivel de Impacto: Mínimo

El objeto data devuelto de useAsyncData, useFetch, useLazyAsyncData y useLazyFetch ahora es un shallowRef en lugar de un ref.

Qué Cambió

Cuando se obtienen nuevos datos, cualquier cosa que dependa de data seguirá siendo reactiva porque se reemplaza todo el objeto. Pero si tu código cambia una propiedad dentro de esa estructura de datos, esto no activará ninguna reactividad en tu aplicación.

Razones para el Cambio

Esto trae una mejora significativa en el rendimiento para objetos y arrays profundamente anidados porque Vue no necesita observar cada propiedad/array para modificaciones. En la mayoría de los casos, data también debería ser inmutable.

Pasos de Migración

En la mayoría de los casos, no se requieren pasos de migración, pero si dependes de la reactividad del objeto de datos, entonces tienes dos opciones:

  1. Puedes optar granularmente por la reactividad profunda en una base por composable:
    - const { data } = useFetch('/api/test')
    + const { data } = useFetch('/api/test', { deep: true })
    
  2. Puedes cambiar el comportamiento predeterminado en todo el proyecto (no recomendado):
    nuxt.config.ts
    export default defineNuxtConfig({
      experimental: {
        defaults: {
          useAsyncData: {
            deep: true
          }
        }
      }
    })
    

Si necesitas, puedes automatizar este paso ejecutando npx codemod@latest nuxt/4/shallow-function-reactivity

Rutas de Observación Absolutas en builder:watch

🚦 Nivel de Impacto: Mínimo

Qué Cambió

El hook builder:watch de Nuxt ahora emite una ruta que es absoluta en lugar de relativa a tu srcDir del proyecto.

Razones para el Cambio

Esto nos permite admitir la observación de rutas que están fuera de tu srcDir, y ofrece un mejor soporte para capas y otros patrones más complejos.

Pasos de Migración

Ya hemos migrado proactivamente los módulos públicos de Nuxt que sabemos que usan este hook. Ver issue #25339.

Sin embargo, si eres un autor de módulos que usa el hook builder:watch y deseas mantener la compatibilidad hacia atrás/adelante, puedes usar el siguiente código para asegurarte de que tu código funcione igual en Nuxt v3 y Nuxt v4:

+ import { relative, resolve } from 'node:fs'
  // ...
  nuxt.hook('builder:watch', async (event, path) => {
+   path = relative(nuxt.options.srcDir, resolve(nuxt.options.srcDir, path))
    // ...
  })

Puedes automatizar este paso ejecutando npx codemod@latest nuxt/4/absolute-watch-path

Eliminación del objeto window.__NUXT__

Qué Cambió

Estamos eliminando el objeto global window.__NUXT__ después de que la aplicación termine la hidratación.

Razones para el Cambio

Esto abre el camino a patrones de múltiples aplicaciones (#21635) y nos permite centrarnos en una única forma de acceder a los datos de la aplicación Nuxt - useNuxtApp().

Pasos de Migración

Los datos aún están disponibles, pero se pueden acceder con useNuxtApp().payload:

- console.log(window.__NUXT__)
+ console.log(useNuxtApp().payload)

Escaneo de Índice de Directorios

🚦 Nivel de Impacto: Medio

Qué Cambió

Las carpetas secundarias en tu carpeta middleware/ también se escanean en busca de archivos index y ahora también se registran como middleware en tu proyecto.

Razones para el Cambio

Nuxt escanea automáticamente una serie de carpetas, incluidas middleware/ y plugins/.

Las carpetas secundarias en tu carpeta plugins/ se escanean en busca de archivos index y queríamos hacer que este comportamiento fuera consistente entre los directorios escaneados.

Pasos de Migración

Probablemente no sea necesaria ninguna migración, pero si deseas volver al comportamiento anterior, puedes agregar un hook para filtrar estos middleware:

export default defineNuxtConfig({
  hooks: {
    'app:resolve'(app) {
      app.middleware = app.middleware.filter(mw => !/\/index\.[^/]+$/.test(mw.path))
    }
  }
})

Cambios en la Compilación de Plantillas

🚦 Nivel de Impacto: Mínimo

Qué Cambió

Anteriormente, Nuxt usaba lodash/template para compilar plantillas ubicadas en el sistema de archivos usando el formato/sintaxis de archivo .ejs.

Además, proporcionamos algunas utilidades de plantilla (serialize, importName, importSources) que podrían usarse para la generación de código dentro de estas plantillas, que ahora se están eliminando.

Razones para el Cambio

En Nuxt v3 nos movimos a una sintaxis 'virtual' con una función getContents() que es mucho más flexible y eficiente.

Además, lodash/template ha tenido una sucesión de problemas de seguridad. Estos realmente no se aplican a los proyectos de Nuxt porque se está utilizando en tiempo de compilación, no en tiempo de ejecución, y por código de confianza. Sin embargo, aún aparecen en auditorías de seguridad. Además, lodash es una dependencia pesada y no es utilizada por la mayoría de los proyectos.

Finalmente, proporcionar funciones de serialización de código directamente dentro de Nuxt no es ideal. En su lugar, mantenemos proyectos como unjs/knitwork que pueden ser dependencias de tu proyecto, y donde los problemas de seguridad pueden ser reportados/resueltos directamente sin requerir una actualización de Nuxt en sí.

Pasos de Migración

Hemos presentado PRs para actualizar módulos que usan sintaxis EJS, pero si necesitas hacer esto tú mismo, tienes tres alternativas compatibles hacia atrás/adelante:

  • Mover tu lógica de interpolación de cadenas directamente a getContents().
  • Usar una función personalizada para manejar el reemplazo, como en https://github.com/nuxt-modules/color-mode/pull/240.
  • Usar es-toolkit/compat (un reemplazo directo para lodash template), como una dependencia de tu proyecto en lugar de Nuxt:
+ import { readFileSync } from 'node:fs'
+ import { template } from 'es-toolkit/compat'
  // ...
  addTemplate({
    fileName: 'appinsights-vue.js'
    options: { /* algunas opciones */ },
-   src: resolver.resolve('./runtime/plugin.ejs'),
+   getContents({ options }) {
+     const contents = readFileSync(resolver.resolve('./runtime/plugin.ejs'), 'utf-8')
+     return template(contents)({ options })
+   },
  })

Finalmente, si estás usando las utilidades de plantilla (serialize, importName, importSources), puedes reemplazarlas de la siguiente manera con utilidades de knitwork:

import { genDynamicImport, genImport, genSafeVariableName } from 'knitwork'

const serialize = (data: any) => JSON.stringify(data, null, 2).replace(/"{(.+)}"(?=,?$)/gm, r => JSON.parse(r).replace(/^{(.*)}$/, '$1'))

const importSources = (sources: string | string[], { lazy = false } = {}) => {
  return toArray(sources).map((src) => {
    if (lazy) {
      return `const ${genSafeVariableName(src)} = ${genDynamicImport(src, { comment: `webpackChunkName: ${JSON.stringify(src)}` })}`
    }
    return genImport(src, genSafeVariableName(src))
  }).join('\n')
}

const importName = genSafeVariableName

Puedes automatizar este paso ejecutando npx codemod@latest nuxt/4/template-compilation-changes

Eliminación de Características Experimentales

🚦 Nivel de Impacto: Mínimo

Qué Cambió

Cuatro características experimentales ya no son configurables en Nuxt 4:

  • experimental.treeshakeClientOnly será true (predeterminado desde v3.0)
  • experimental.configSchema será true (predeterminado desde v3.3)
  • experimental.polyfillVueUseHead será false (predeterminado desde v3.4)
  • experimental.respectNoSSRHeader será false (predeterminado desde v3.4)
  • vite.devBundler ya no es configurable - usará vite-node por defecto

Razones para el Cambio

Estas opciones han estado configuradas en sus valores actuales durante algún tiempo y no tenemos razones para creer que necesiten seguir siendo configurables.

Pasos de Migración

Nuxt 2 vs. Nuxt 3+

En la tabla a continuación, hay una comparación rápida entre 3 versiones de Nuxt:

Característica / VersiónNuxt 2Nuxt BridgeNuxt 3+
Vue223
Estabilidad😊 Estable😊 Estable😊 Estable
Rendimiento🏎 Rápido✈️ Más rápido🚀 Más rápido
Motor Nitro
Soporte ESM🌙 Parcial👍 Mejor
TypeScript☑️ Opt-in🚧 Parcial
API de Composición🚧 Parcial
API de Opciones
Importación Automática de Componentes
Sintaxis <script setup>🚧 Parcial
Importaciones Automáticas
webpack445
Vite⚠️ Parcial🚧 Parcial
CLI de Nuxt❌ Antiguo✅ nuxt✅ nuxt
Sitios estáticos

Nuxt 2 a Nuxt 3+

La guía de migración proporciona una comparación paso a paso de las características de Nuxt 2 a las características de Nuxt 3+ y orientación para adaptar tu aplicación actual.

Ver también migration > overview

Nuxt 2 a Nuxt Bridge

Si prefieres migrar progresivamente tu aplicación Nuxt 2 a Nuxt 3, puedes usar Nuxt Bridge. Nuxt Bridge es una capa de compatibilidad que te permite usar características de Nuxt 3+ en Nuxt 2 con un mecanismo de opt-in.

Ver también bridge > overview