nuxt logo

Traducción de Documentación (No Oficial)

Obtención de Datos

Nuxt proporciona composables para manejar la obtención de datos dentro de tu aplicación.

Nuxt viene con dos composables y una biblioteca integrada para realizar la obtención de datos en entornos de navegador o servidor: useFetch, useAsyncData y $fetch.

En resumen:

  • $fetch es la forma más sencilla de realizar una solicitud de red.
  • useFetch es un envoltorio alrededor de $fetch que obtiene datos solo una vez en renderizado universal.
  • useAsyncData es similar a useFetch pero ofrece un control más detallado.

Tanto useFetch como useAsyncData comparten un conjunto común de opciones y patrones que detallaremos en las últimas secciones.

La necesidad de useFetch y useAsyncData

Nuxt es un framework que puede ejecutar código isomórfico (o universal) en entornos tanto de servidor como de cliente. Si se utiliza la función $fetch para realizar la obtención de datos en la función de configuración de un componente Vue, esto puede causar que los datos se obtengan dos veces, una vez en el servidor (para renderizar el HTML) y otra vez en el cliente (cuando el HTML se hidrata). Esto puede causar problemas de hidratación, aumentar el tiempo de interactividad y causar un comportamiento impredecible.

Los composables useFetch y useAsyncData resuelven este problema asegurando que si se realiza una llamada a la API en el servidor, los datos se envían al cliente en la carga útil.

La carga útil es un objeto JavaScript accesible a través de useNuxtApp().payload. Se utiliza en el cliente para evitar volver a obtener los mismos datos cuando el código se ejecuta en el navegador durante la hidratación.

Usa las Herramientas de Desarrollo de Nuxt para inspeccionar estos datos en la pestaña de Carga Útil.

app.vue
<script setup lang="ts">
const { data } = await useFetch('/api/data')

async function handleFormSubmit() {
  const res = await $fetch('/api/submit', {
    method: 'POST',
    body: {
      // Mis datos del formulario
    }
  })
}
</script>

<template>
  <div v-if="data == null">
    No hay datos
  </div>
  <div v-else>
    <form @submit="handleFormSubmit">
      <!-- etiquetas de entrada del formulario -->
    </form>
  </div>
</template>

En el ejemplo anterior, useFetch se aseguraría de que la solicitud ocurra en el servidor y se envíe correctamente al navegador. $fetch no tiene tal mecanismo y es una mejor opción para usar cuando la solicitud se realiza únicamente desde el navegador.

Suspense

Nuxt utiliza el componente <Suspense> de Vue para evitar la navegación antes de que todos los datos asincrónicos estén disponibles para la vista. Los composables de obtención de datos pueden ayudarte a aprovechar esta característica y usar lo que mejor se adapte en cada llamada.

Puedes agregar el <NuxtLoadingIndicator> para agregar una barra de progreso entre las navegaciones de página.

$fetch

Nuxt incluye la biblioteca ofetch, y se importa automáticamente como el alias $fetch globalmente en tu aplicación.

pages/todos.vue
async function addTodo() {
  const todo = await $fetch('/api/todos', {
    method: 'POST',
    body: {
      // Mis datos de todo
    }
  })
}

Ten en cuenta que usar solo $fetch no proporcionará deduplicación de llamadas de red y prevención de navegación. :br Se recomienda usar $fetch para interacciones del lado del cliente (basadas en eventos) o combinado con useAsyncData al obtener los datos iniciales del componente.

Ver también api > utils > dollarfetch

Pasar encabezados del cliente a la API

Al llamar a useFetch en el servidor, Nuxt usará useRequestFetch para enviar encabezados y cookies del cliente (con la excepción de encabezados que no deben ser reenviados, como host).

const { data } = await useFetch('/api/echo');
// /api/echo.ts
export default defineEventHandler(event => parseCookies(event))

Alternativamente, el ejemplo a continuación muestra cómo usar useRequestHeaders para acceder y enviar cookies a la API desde una solicitud del lado del servidor (originada en el cliente). Usando una llamada isomórfica $fetch, aseguramos que el punto final de la API tenga acceso al mismo encabezado cookie enviado originalmente por el navegador del usuario. Esto solo es necesario si no estás usando useFetch.

const headers = useRequestHeaders(['cookie'])

async function getCurrentUser() {
  return await $fetch('/api/me', { headers })
}

También puedes usar useRequestFetch para enviar encabezados a la llamada automáticamente.

Ten mucho cuidado antes de enviar encabezados a una API externa e incluye solo los encabezados que necesitas. No todos los encabezados son seguros para ser pasados y podrían introducir un comportamiento no deseado. Aquí hay una lista de encabezados comunes que NO deben ser enviados:

  • host, accept
  • content-length, content-md5, content-type
  • x-forwarded-host, x-forwarded-port, x-forwarded-proto
  • cf-connecting-ip, cf-ray

useFetch

El composable useFetch utiliza $fetch internamente para realizar llamadas de red seguras para SSR en la función de configuración.

app.vue
<script setup lang="ts">
const { data: count } = await useFetch('/api/count')
</script>

<template>
  <p>Visitas a la página: {{ count }}</p>
</template>

Este composable es un envoltorio alrededor del composable useAsyncData y la utilidad $fetch.

Ver también api > composables > use-fetch
Editar y previsualizar el código de ejemploexamples > features > data-fetching

useAsyncData

El composable useAsyncData es responsable de envolver la lógica asincrónica y devolver el resultado una vez que se resuelve.

useFetch(url) es casi equivalente a useAsyncData(url, () => event.$fetch(url)). :br Es un azúcar para la experiencia del desarrollador para el caso de uso más común. (Puedes encontrar más sobre event.fetch en useRequestFetch.)

Hay algunos casos en los que usar el composable useFetch no es apropiado, por ejemplo, cuando un CMS o un tercero proporciona su propia capa de consulta. En este caso, puedes usar useAsyncData para envolver tus llamadas y seguir manteniendo los beneficios proporcionados por el composable.

pages/users.vue
const { data, error } = await useAsyncData('users', () => myGetFunction('users'))

// Esto también es posible:
const { data, error } = await useAsyncData(() => myGetFunction('users'))

El primer argumento de useAsyncData es una clave única utilizada para almacenar en caché la respuesta de la función de consulta del segundo argumento. Esta clave puede ser ignorada pasando directamente la función de consulta, la clave se generará automáticamente. :br :br Dado que la clave autogenerada solo tiene en cuenta el archivo y la línea donde se invoca useAsyncData, se recomienda siempre crear tu propia clave para evitar comportamientos no deseados, como cuando estás creando tu propio composable personalizado que envuelve useAsyncData. :br :br Establecer una clave puede ser útil para compartir los mismos datos entre componentes usando useNuxtData o para refrescar datos específicos.

pages/users/[id\
const { id } = useRoute().params

const { data, error } = await useAsyncData(`user:${id}`, () => {
  return myGetFunction('users', { id })
})

El composable useAsyncData es una excelente manera de envolver y esperar a que se completen múltiples solicitudes $fetch, y luego procesar los resultados.

const { data: discounts, status } = await useAsyncData('cart-discount', async () => {
  const [coupons, offers] = await Promise.all([
    $fetch('/cart/coupons'),
    $fetch('/cart/offers')
  ])

  return { coupons, offers }
})
// discounts.value.coupons
// discounts.value.offers

useAsyncData es para obtener y almacenar en caché datos, no para desencadenar efectos secundarios como llamar a acciones de Pinia, ya que esto puede causar un comportamiento no deseado, como ejecuciones repetidas con valores nulos. Si necesitas desencadenar efectos secundarios, usa la utilidad callOnce para hacerlo.

const offersStore = useOffersStore()

// no puedes hacer esto
await useAsyncData(() => offersStore.getOffer(route.params.slug))
Ver también api > composables > use-async-data

Valores de Retorno

useFetch y useAsyncData tienen los mismos valores de retorno listados a continuación.

  • data: el resultado de la función asincrónica que se pasa.
  • refresh/execute: una función que se puede usar para refrescar los datos devueltos por la función handler.
  • clear: una función que se puede usar para establecer data en undefined (o el valor de options.default() si se proporciona), establecer error en null, establecer status en idle, y marcar cualquier solicitud pendiente como cancelada.
  • error: un objeto de error si la obtención de datos falló.
  • status: una cadena que indica el estado de la solicitud de datos ("idle", "pending", "success", "error").

data, error y status son referencias de Vue accesibles con .value en <script setup>

Por defecto, Nuxt espera hasta que un refresh se complete antes de que pueda ejecutarse nuevamente.

Si no has obtenido datos en el servidor (por ejemplo, con server: false), entonces los datos no se obtendrán hasta que la hidratación se complete. Esto significa que incluso si esperas useFetch en el lado del cliente, data permanecerá nulo dentro de <script setup>.

Opciones

useAsyncData y useFetch devuelven el mismo tipo de objeto y aceptan un conjunto común de opciones como su último argumento. Pueden ayudarte a controlar el comportamiento de los composables, como el bloqueo de navegación, el almacenamiento en caché o la ejecución.

Lazy

Por defecto, los composables de obtención de datos esperarán la resolución de su función asincrónica antes de navegar a una nueva página usando el Suspense de Vue. Esta característica puede ser ignorada en la navegación del lado del cliente con la opción lazy. En ese caso, tendrás que manejar manualmente el estado de carga usando el valor status.

app.vue
<script setup lang="ts">
const { status, data: posts } = useFetch('/api/posts', {
  lazy: true
})
</script>

<template>
  <!-- tendrás que manejar un estado de carga -->
  <div v-if="status === 'pending'">
    Cargando ...
  </div>
  <div v-else>
    <div v-for="post in posts">
      <!-- hacer algo -->
    </div>
  </div>
</template>

Alternativamente, puedes usar useLazyFetch y useLazyAsyncData como métodos convenientes para realizar lo mismo.

const { status, data: posts } = useLazyFetch('/api/posts')
Ver también api > composables > use-lazy-fetch Ver también api > composables > use-lazy-async-data

Obtención solo del lado del cliente

Por defecto, los composables de obtención de datos realizarán su función asincrónica en entornos tanto de cliente como de servidor. Establece la opción server en false para realizar la llamada solo en el lado del cliente. En la carga inicial, los datos no se obtendrán antes de que la hidratación esté completa, por lo que tendrás que manejar un estado pendiente, aunque en la navegación posterior del lado del cliente los datos se esperarán antes de cargar la página.

Combinado con la opción lazy, esto puede ser útil para datos que no se necesitan en el primer renderizado (por ejemplo, datos no sensibles a SEO).

/* Esta llamada se realiza antes de la hidratación */
const articles = await useFetch('/api/article')

/* Esta llamada solo se realizará en el cliente */
const { status, data: comments } = useFetch('/api/comments', {
  lazy: true,
  server: false
})

El composable useFetch está destinado a ser invocado en el método de configuración o llamado directamente en el nivel superior de una función en los hooks del ciclo de vida, de lo contrario, deberías usar el método $fetch.

Minimizar el tamaño de la carga útil

La opción pick te ayuda a minimizar el tamaño de la carga útil almacenada en tu documento HTML seleccionando solo los campos que deseas que devuelvan los composables.

<script setup lang="ts">
/* solo selecciona los campos utilizados en tu plantilla */
const { data: mountain } = await useFetch('/api/mountains/everest', {
  pick: ['title', 'description']
})
</script>

<template>
  <h1>{{ mountain.title }}</h1>
  <p>{{ mountain.description }}</p>
</template>

Si necesitas más control o mapear sobre varios objetos, puedes usar la función transform para alterar el resultado de la consulta.

const { data: mountains } = await useFetch('/api/mountains', {
  transform: (mountains) => {
    return mountains.map(mountain => ({ title: mountain.title, description: mountain.description }))
  }
})

Tanto pick como transform no evitan que los datos no deseados se obtengan inicialmente. Pero evitarán que los datos no deseados se agreguen a la carga útil transferida del servidor al cliente.

Almacenamiento en caché y reobtención

Claves

useFetch y useAsyncData usan claves para evitar volver a obtener los mismos datos.

  • useFetch usa la URL proporcionada como clave. Alternativamente, se puede proporcionar un valor key en el objeto options pasado como último argumento.
  • useAsyncData usa su primer argumento como clave si es una cadena. Si el primer argumento es la función manejadora que realiza la consulta, entonces se generará una clave única para el nombre del archivo y el número de línea de la instancia de useAsyncData.

Para obtener los datos almacenados en caché por clave, puedes usar useNuxtData

Estado compartido y consistencia de opciones

Cuando varios componentes usan la misma clave con useAsyncData o useFetch, compartirán las mismas referencias data, error y status. Esto asegura consistencia entre componentes pero requiere que algunas opciones sean consistentes.

Las siguientes opciones deben ser consistentes en todas las llamadas con la misma clave:

  • Función handler
  • Opción deep
  • Función transform
  • Array pick
  • Función getCachedData
  • Valor default
// ❌ Esto generará una advertencia de desarrollo
const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { deep: false })
const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { deep: true })

Las siguientes opciones pueden diferir de manera segura sin generar advertencias:

  • server
  • lazy
  • immediate
  • dedupe
  • watch
// ✅ Esto está permitido
const { data: users1 } = useAsyncData('users', () => $fetch('/api/users'), { immediate: true })
const { data: users2 } = useAsyncData('users', () => $fetch('/api/users'), { immediate: false })

Si necesitas instancias independientes, usa diferentes claves:

// Estas son instancias completamente independientes
const { data: users1 } = useAsyncData('users-1', () => $fetch('/api/users'))
const { data: users2 } = useAsyncData('users-2', () => $fetch('/api/users'))

Claves reactivas

Puedes usar referencias computadas, referencias simples o funciones getter como claves, permitiendo la obtención de datos dinámica que se actualiza automáticamente cuando cambian las dependencias:

// Usando una propiedad computada como clave
const userId = ref('123')
const { data: user } = useAsyncData(
  computed(() => `user-${userId.value}`),
  () => fetchUser(userId.value)
)

// Cuando userId cambia, los datos se volverán a obtener automáticamente
// y los datos antiguos se limpiarán si ningún otro componente los usa
userId.value = '456'

Refrescar y ejecutar

Si deseas obtener o refrescar datos manualmente, usa la función execute o refresh proporcionada por los composables.

<script setup lang="ts">
const { data, error, execute, refresh } = await useFetch('/api/users')
</script>

<template>
  <div>
    <p>{{ data }}</p>
    <button @click="() => refresh()">Refrescar datos</button>
  </div>
</template>

La función execute es un alias para refresh que funciona exactamente de la misma manera pero es más semántica para casos cuando la obtención no es inmediata.

Para volver a obtener o invalidar datos almacenados en caché globalmente, consulta clearNuxtData y refreshNuxtData.

Limpiar

Si deseas limpiar los datos proporcionados, por cualquier razón, sin necesidad de conocer la clave específica para pasar a clearNuxtData, puedes usar la función clear proporcionada por los composables.

const { data, clear } = await useFetch('/api/users')

const route = useRoute()
watch(() => route.path, (path) => {
  if (path === '/') clear()
})

Watch

Para volver a ejecutar tu función de obtención cada vez que cambien otros valores reactivos en tu aplicación, usa la opción watch. Puedes usarla para uno o varios elementos observables.

const id = ref(1)

const { data, error, refresh } = await useFetch('/api/users', {
  /* Cambiar el id desencadenará una nueva obtención */
  watch: [id]
})

Ten en cuenta que observar un valor reactivo no cambiará la URL obtenida. Por ejemplo, esto seguirá obteniendo el mismo ID inicial del usuario porque la URL se construye en el momento en que se invoca la función.

const id = ref(1)

const { data, error, refresh } = await useFetch(`/api/users/${id.value}`, {
  watch: [id]
})

Si necesitas cambiar la URL basada en un valor reactivo, es posible que desees usar una URL computada en su lugar.

URL Computada

A veces puedes necesitar calcular una URL a partir de valores reactivos, y refrescar los datos cada vez que estos cambien. En lugar de lidiar con esto, puedes adjuntar cada parámetro como un valor reactivo. Nuxt usará automáticamente el valor reactivo y volverá a obtener cada vez que cambie.

const id = ref(null)

const { data, status } = useLazyFetch('/api/user', {
  query: {
    user_id: id
  }
})

En el caso de una construcción de URL más compleja, puedes usar un callback como un getter computado que devuelve la cadena de URL.

Cada vez que una dependencia cambie, los datos se obtendrán usando la URL recién construida. Combina esto con no inmediato, y puedes esperar hasta que el elemento reactivo cambie antes de obtener.

<script setup lang="ts">
const id = ref(null)

const { data, status } = useLazyFetch(() => `/api/users/${id.value}`, {
  immediate: false
})

const pending = computed(() => status.value === 'pending');
</script>

<template>
  <div>
    {/* deshabilitar la entrada mientras se obtiene */}
    <input v-model="id" type="number" :disabled="pending"/>

    <div v-if="status === 'idle'">
      Escribe un ID de usuario
    </div>

    <div v-else-if="pending">
      Cargando ...
    </div>

    <div v-else>
      {{ data }}
    </div>
  </div>
</template>

Si necesitas forzar un refresco cuando cambien otros valores reactivos, también puedes observar otros valores.

No inmediato

El composable useFetch comenzará a obtener datos en el momento en que se invoque. Puedes prevenir esto estableciendo immediate: false, por ejemplo, para esperar la interacción del usuario.

Con eso, necesitarás tanto el status para manejar el ciclo de vida de la obtención, como execute para iniciar la obtención de datos.

<script setup lang="ts">
const { data, error, execute, status } = await useLazyFetch('/api/comments', {
  immediate: false
})
</script>

<template>
  <div v-if="status === 'idle'">
    <button @click="execute">Obtener datos</button>
  </div>

  <div v-else-if="status === 'pending'">
    Cargando comentarios...
  </div>

  <div v-else>
    {{ data }}
  </div>
</template>

Para un control más fino, la variable status puede ser:

  • idle cuando la obtención no ha comenzado
  • pending cuando una obtención ha comenzado pero aún no se ha completado
  • error cuando la obtención falla
  • success cuando la obtención se completa con éxito

Pasando Encabezados y Cookies

Cuando llamamos a $fetch en el navegador, los encabezados del usuario como cookie se enviarán directamente a la API.

Normalmente, durante el renderizado del lado del servidor, por consideraciones de seguridad, $fetch no incluiría las cookies del navegador del usuario, ni pasaría las cookies de la respuesta de obtención.

Sin embargo, al llamar a useFetch con una URL relativa en el servidor, Nuxt usará useRequestFetch para enviar encabezados y cookies (con la excepción de encabezados que no deben ser reenviados, como host).

Pasar Cookies desde Llamadas a la API del Lado del Servidor en la Respuesta SSR

Si deseas pasar o enviar cookies en la otra dirección, desde una solicitud interna de vuelta al cliente, necesitarás manejar esto tú mismo.

composables/fetch.ts
import { appendResponseHeader } from 'h3'
import type { H3Event } from 'h3'

export const fetchWithCookie = async (event: H3Event, url: string) => {
  /* Obtener la respuesta del punto final del servidor */
  const res = await $fetch.raw(url)
  /* Obtener las cookies de la respuesta */
  const cookies = res.headers.getSetCookie()
  /* Adjuntar cada cookie a nuestra solicitud entrante */
  for (const cookie of cookies) {
    appendResponseHeader(event, 'set-cookie', cookie)
  }
  /* Devolver los datos de la respuesta */
  return res._data
}
// Este composable pasará automáticamente cookies al cliente
const event = useRequestEvent()

const { data: result } = await useAsyncData(() => fetchWithCookie(event!, '/api/with-cookie'))

onMounted(() => console.log(document.cookie))

Soporte para la API de Opciones

Nuxt proporciona una forma de realizar la obtención de asyncData dentro de la API de Opciones. Debes envolver la definición de tu componente dentro de defineNuxtComponent para que esto funcione.

export default defineNuxtComponent({
  /* Usa la opción fetchKey para proporcionar una clave única */
  fetchKey: 'hello',
  async asyncData () {
    return {
      hello: await $fetch('/api/hello')
    }
  }
})

Usar <script setup> o <script setup lang="ts"> son las formas recomendadas de declarar componentes Vue en Nuxt.

Ver también api > utils > define-nuxt-component

Serialización de Datos del Servidor al Cliente

Al usar useAsyncData y useLazyAsyncData para transferir datos obtenidos en el servidor al cliente (así como cualquier otra cosa que utilice la carga útil de Nuxt), la carga útil se serializa con devalue. Esto nos permite transferir no solo JSON básico sino también serializar y revivir/deserializar tipos de datos más avanzados, como expresiones regulares, Fechas, Map y Set, ref, reactive, shallowRef, shallowReactive y NuxtError, entre otros.

También es posible definir tu propio serializador/deserializador para tipos que no son compatibles con Nuxt. Puedes leer más en los documentos de useNuxtApp.

Ten en cuenta que esto no se aplica a los datos pasados desde tus rutas del servidor cuando se obtienen con $fetch o useFetch - consulta la siguiente sección para más información.

Serialización de Datos desde Rutas de API

Al obtener datos desde el directorio server, la respuesta se serializa usando JSON.stringify. Sin embargo, dado que la serialización está limitada solo a tipos primitivos de JavaScript, Nuxt hace su mejor esfuerzo para convertir el tipo de retorno de $fetch y useFetch para que coincida con el valor real.

Ver también developer.mozilla.org > en-US > docs > Web > JavaScript > Reference > Global_Objects > JSON > stringify

Ejemplo

server/api/foo.ts
export default defineEventHandler(() => {
  return new Date()
})
app.vue
// El tipo de `data` se infiere como cadena aunque devolvimos un objeto Date
const { data } = await useFetch('/api/foo')

Función de serializador personalizada

Para personalizar el comportamiento de la serialización, puedes definir una función toJSON en tu objeto devuelto. Si defines un método toJSON, Nuxt respetará el tipo de retorno de la función y no intentará convertir los tipos.

server/api/bar.ts
export default defineEventHandler(() => {
  const data = {
    createdAt: new Date(),

    toJSON() {
      return {
        createdAt: {
          year: this.createdAt.getFullYear(),
          month: this.createdAt.getMonth(),
          day: this.createdAt.getDate(),
        },
      }
    },
  }
  return data
})

app.vue
// El tipo de `data` se infiere como
// {
//   createdAt: {
//     year: number
//     month: number
//     day: number
//   }
// }
const { data } = await useFetch('/api/bar')

Usando un serializador alternativo

Nuxt actualmente no soporta un serializador alternativo a JSON.stringify. Sin embargo, puedes devolver tu carga útil como una cadena normal y utilizar el método toJSON para mantener la seguridad de tipos.

En el ejemplo a continuación, usamos superjson como nuestro serializador.

server/api/superjson.ts
import superjson from 'superjson'

export default defineEventHandler(() => {
  const data = {
    createdAt: new Date(),

    // Solución para la conversión de tipos
    toJSON() {
      return this
    }
  }

  // Serializar la salida a cadena, usando superjson
  return superjson.stringify(data) as unknown as typeof data
})
app.vue
import superjson from 'superjson'

// `date` se infiere como { createdAt: Date } y puedes usar de manera segura los métodos del objeto Date
const { data } = await useFetch('/api/superjson', {
  transform: (value) => {
    return superjson.parse(value as unknown as string)
  },
})

Recetas

Consumiendo SSE (Eventos Enviados por el Servidor) a través de una solicitud POST

Si estás consumiendo SSE a través de una solicitud GET, puedes usar EventSource o el composable de VueUse useEventSource.

Al consumir SSE a través de una solicitud POST, necesitas manejar la conexión manualmente. Aquí te mostramos cómo hacerlo:

// Realizar una solicitud POST al punto final SSE
const response = await $fetch<ReadableStream>('/chats/ask-ai', {
  method: 'POST',
  body: {
    query: "Hola AI, ¿cómo estás?",
  },
  responseType: 'stream',
})

// Crear un nuevo ReadableStream a partir de la respuesta con TextDecoderStream para obtener los datos como texto
const reader = response.pipeThrough(new TextDecoderStream()).getReader()

// Leer el fragmento de datos a medida que lo obtenemos
while (true) {
  const { value, done } = await reader.read()

  if (done)
    break

  console.log('Recibido:', value)
}

Realizando solicitudes en paralelo

Cuando las solicitudes no dependen unas de otras, puedes realizarlas en paralelo con Promise.all() para mejorar el rendimiento.

const { data } = await useAsyncData(() => {
  return Promise.all([
    $fetch("/api/comments/"), 
    $fetch("/api/author/12")
  ]);
});

const comments = computed(() => data.value?.[0]);
const author = computed(() => data.value?.[1]);