Vue 3 Teleport y Suspense: Patrones Avanzados y Preguntas de Entrevista 2026

Domina Teleport y Suspense en Vue 3.5 con patrones de producción, manejo de errores y las preguntas de entrevista técnica más frecuentes para desarrolladores Vue.

Vue 3 Teleport y Suspense patrones avanzados

Vue 3 Teleport y Suspense resuelven dos desafíos de renderizado distintos: ubicar componentes fuera de su jerarquía DOM y coordinar dependencias asíncronas. Ambos son componentes nativos que no requieren importaciones y aparecen frecuentemente en entrevistas para desarrolladores Vue senior.

Actualización de Teleport en Vue 3.5

Vue 3.5 agregó la prop defer a Teleport, permitiendo apuntar a elementos renderizados posteriormente en el árbol de componentes. Esto resuelve el problema común de timing en SSR donde el destino del portal no existe al momento del montaje.

Fundamentos de Teleport y la Prop defer

Teleport mueve el contenido renderizado a una ubicación DOM diferente mientras preserva el árbol de componentes Vue. El componente permanece como hijo lógico de su padre: props, eventos y provide/inject funcionan normalmente. Vue DevTools muestra el componente teleportado anidado bajo su padre, no bajo el elemento destino.

vue
<!-- ModalTrigger.vue -->
<script setup>
import { ref } from 'vue'

const isOpen = ref(false)
</script>

<template>
  <button @click="isOpen = true">Open Modal</button>

  <Teleport to="#modal-root">
    <div v-if="isOpen" class="modal-overlay" @click.self="isOpen = false">
      <div class="modal-content">
        <slot />
        <button @click="isOpen = false">Close</button>
      </div>
    </div>
  </Teleport>
</template>

La prop to acepta una cadena de selector CSS o una referencia a elemento DOM. Cuando el destino no existe al momento del montaje, Teleport emite una advertencia y no renderiza nada. Esto crea problemas con SSR y layouts dinámicos.

La Prop defer (Vue 3.5+)

La prop defer indica a Teleport que espere hasta que otras partes de la aplicación se hayan montado antes de resolver el destino. Esto permite teleportar hacia elementos renderizados posteriormente en el mismo componente o en componentes hermanos.

vue
<!-- App.vue -->
<template>
  <Teleport defer to="#dynamic-target">
    <NotificationBanner />
  </Teleport>

  <!-- Target rendered after the Teleport -->
  <div id="dynamic-target"></div>
</template>

Sin defer, este código falla porque #dynamic-target no existe cuando Teleport se monta. Con defer, Vue resuelve el destino después de que todos los componentes en el mismo tick se hayan montado. El destino aún debe renderizarse en el mismo ciclo de montaje o actualización.

Múltiples Teleports y Renderizado Condicional

Múltiples componentes Teleport pueden apuntar al mismo elemento. El contenido se agrega en orden de declaración:

vue
<template>
  <Teleport to="#notifications">
    <Toast message="First" />
  </Teleport>

  <Teleport to="#notifications">
    <Toast message="Second" />
  </Teleport>
</template>

<!-- Result in #notifications:
  <div>First</div>
  <div>Second</div>
-->

La prop disabled controla si la teleportación ocurre. Cuando está deshabilitada, el contenido se renderiza en su lugar original. Este patrón maneja layouts responsivos donde los modales deben superponerse en escritorio pero renderizarse en línea en móvil:

vue
<script setup>
import { useMediaQuery } from '@vueuse/core'

const isMobile = useMediaQuery('(max-width: 768px)')
</script>

<template>
  <Teleport to="body" :disabled="isMobile">
    <MobileDrawer />
  </Teleport>
</template>

Suspense para Orquestación de Componentes Asíncronos

Suspense coordina dependencias asíncronas a través de un árbol de componentes. Cuando cualquier componente descendiente tiene una dependencia asíncrona sin resolver, Suspense muestra el contenido del slot fallback. Una vez que todas las dependencias se resuelven, Suspense cambia al slot por defecto.

Estado Experimental

Suspense permanece experimental en Vue 3.5. La API puede cambiar antes de alcanzar estado estable. El uso en producción requiere aceptar este riesgo.

vue
<!-- Dashboard.vue -->
<template>
  <Suspense>
    <template #default>
      <DashboardContent />
    </template>

    <template #fallback>
      <LoadingSpinner />
    </template>
  </Suspense>
</template>

Suspense rastrea dos tipos de dependencias asíncronas: componentes con async setup() y componentes asíncronos creados con defineAsyncComponent.

Setup Asíncrono con Top-Level Await

Vue 3 script setup soporta top-level await. Cualquier componente que use top-level await se convierte en una dependencia asíncrona que Suspense puede rastrear:

vue
<!-- UserProfile.vue -->
<script setup>
const response = await fetch('/api/user/profile')
const user = await response.json()
</script>

<template>
  <div class="profile">
    <h1>{{ user.name }}</h1>
    <p>{{ user.email }}</p>
  </div>
</template>

Este componente no puede renderizarse hasta que el fetch complete. Un boundary Suspense padre muestra el fallback hasta la resolución. Sin un boundary Suspense, el componente simplemente no se renderiza.

Suspense Anidado con la Prop suspensible

Las aplicaciones complejas frecuentemente tienen múltiples boundaries asíncronos. Vue 3.3 introdujo la prop suspensible para controlar cómo los componentes Suspense anidados interactúan con sus padres.

vue
<!-- PageLayout.vue -->
<template>
  <Suspense>
    <template #default>
      <header>
        <AsyncNavigation />
      </header>

      <main>
        <!-- Inner Suspense with its own fallback -->
        <Suspense suspensible>
          <template #default>
            <AsyncContent />
          </template>
          <template #fallback>
            <ContentSkeleton />
          </template>
        </Suspense>
      </main>
    </template>

    <template #fallback>
      <PageSkeleton />
    </template>
  </Suspense>
</template>

Cuando suspensible está configurado, el Suspense interno se registra con el boundary Suspense padre. El padre espera a que tanto AsyncNavigation como AsyncContent se resuelvan antes de ocultar su fallback. El Suspense interno aún sirve como boundary local: si AsyncContent toma más tiempo, ContentSkeleton se muestra mientras AsyncNavigation permanece visible.

Sin suspensible, el Suspense interno opera independientemente. El padre muestra su fallback solo mientras AsyncNavigation carga, ignorando AsyncContent completamente.

¿Listo para aprobar tus entrevistas de Vue.js / Nuxt.js?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Combinando Teleport, Suspense y Transiciones

Las aplicaciones reales combinan estos patrones. Un modal que carga contenido asincrónicamente se beneficia de los tres:

vue
<!-- AsyncModal.vue -->
<script setup>
import { ref, defineAsyncComponent } from 'vue'

const isOpen = ref(false)

const AsyncModalContent = defineAsyncComponent({
  loader: () => import('./ModalContent.vue'),
  loadingComponent: () => import('./ModalSkeleton.vue'),
  delay: 200,
  timeout: 10000
})
</script>

<template>
  <button @click="isOpen = true">Open</button>

  <Teleport to="body">
    <Transition name="modal">
      <div v-if="isOpen" class="modal-wrapper">
        <Suspense>
          <template #default>
            <AsyncModalContent @close="isOpen = false" />
          </template>
          <template #fallback>
            <ModalSkeleton />
          </template>
        </Suspense>
      </div>
    </Transition>
  </Teleport>
</template>

<style>
.modal-enter-active,
.modal-leave-active {
  transition: opacity 0.3s ease;
}

.modal-enter-from,
.modal-leave-to {
  opacity: 0;
}
</style>

El orden de anidación importa: Teleport envuelve Transition, que envuelve el elemento condicional, que contiene Suspense. Esto asegura que la transición anime todo el modal incluyendo estados de carga.

Manejo de Errores con Suspense

Suspense no maneja errores. Las dependencias asíncronas fallidas rechazan sus promesas y propagan errores hacia arriba en el árbol de componentes. El hook de ciclo de vida onErrorCaptured captura estos errores en el boundary:

vue
<!-- ErrorBoundary.vue -->
<script setup>
import { ref, onErrorCaptured } from 'vue'

const error = ref(null)

onErrorCaptured((err) => {
  error.value = err
  return false // Prevent propagation
})
</script>

<template>
  <div v-if="error" class="error-state">
    <p>Failed to load: {{ error.message }}</p>
    <button @click="error = null">Retry</button>
  </div>

  <Suspense v-else>
    <template #default>
      <slot />
    </template>
    <template #fallback>
      <slot name="loading" />
    </template>
  </Suspense>
</template>

Este patrón envuelve cualquier árbol de componentes asíncronos con estados tanto de carga como de error. El botón de reintentar limpia el error, causando que Vue re-renderice y re-intente la operación asíncrona.

Preguntas de Entrevista sobre Teleport y Suspense

Las posiciones Vue senior prueban estos patrones cada vez más. Preguntas comunes y lo que distingue respuestas sólidas:

P: ¿Cuándo debería usarse Teleport sobre posicionamiento CSS?

Respuesta débil: "Cuando z-index no funciona."

Respuesta sólida: Teleport resuelve problemas de jerarquía DOM que CSS no puede. Un modal dentro de un contenedor con overflow: hidden o transform crea un nuevo contexto de apilamiento, rompiendo position: fixed. Teleport mueve el modal a body, escapando de estas restricciones mientras preserva el árbol lógico de componentes Vue.

P: ¿Cómo difiere Suspense de estados de carga en cada componente?

Respuesta débil: "Suspense es más limpio."

Respuesta sólida: Suspense coordina múltiples dependencias asíncronas. Diez componentes con estados de carga individuales muestran diez spinners en diferentes momentos. Suspense muestra un spinner hasta que los diez resuelven, luego los revela juntos. Esto crea un rendimiento percibido más suave y código de componente más simple ya que los componentes asíncronos no necesitan manejar sus propios estados de carga.

P: ¿Qué sucede cuando un destino Teleport no existe?

El contenido no se renderiza y Vue registra una advertencia. La prop defer de Vue 3.5 aborda esto esperando a que otros componentes se monten primero. Sin defer, hay que asegurar que los destinos existan en index.html o en un componente padre que se monte antes que los hijos.

P: ¿Puede Suspense funcionar con Options API?

Sí. Cualquier componente con async setup() activa Suspense, independientemente de si usa Composition API u Options API en otro lugar. La función setup es el boundary asíncrono, no el estilo del componente.

Teleport y Suspense en Nuxt 3

Nuxt 3 extiende estos patrones con consideraciones SSR. La documentación de Nuxt sobre SSR cubre el timing de hidratación.

Para Teleport, Nuxt requiere que el destino exista antes de la hidratación. Se deben agregar destinos de portal a app.vue o usar la prop defer:

vue
<!-- app.vue -->
<template>
  <NuxtLayout>
    <NuxtPage />
  </NuxtLayout>

  <!-- Portal targets for SSR -->
  <div id="modal-root"></div>
  <div id="toast-root"></div>
</template>

Para Suspense, useFetch y useAsyncData de Nuxt se integran automáticamente. Los componentes de página que usan estos composables funcionan con el Suspense incorporado de Nuxt sin configuración manual. Consulta el módulo de composables Vue de SharpSkill para preparación relacionada a entrevistas.

Patrones de Producción para Vue 3.5

  • Destinos Teleport en index.html: Crear destinos de portal estables que existan antes de que cualquier código Vue se ejecute. Esto evita problemas de timing por completo.
  • Boundaries Suspense granulares: Un Suspense por unidad de carga independiente. Un dashboard con widgets separados se beneficia de boundaries Suspense separados para que widgets rápidos aparezcan inmediatamente.
  • Boundaries de error sobre Suspense: Siempre envolver Suspense con manejo de errores. Fallos de red y errores de API no deben crashear el árbol completo.
  • Timing de transiciones: Al combinar Suspense con Transition, configurar mode="out-in" para prevenir superposición durante cambios de estado.

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

Reto diario

¿Sabrías detectar el bug en Vue.js / Nuxt.js?

Un fragmento real, un bug oculto, un intento al día. Sin cuenta para probar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador de SharpSkill

Desarrollador fullstack desde hace más de 10 años. Dirige SharpSkill y responde por todo lo que se publica aquí.

Actualizado el 28 de agosto de 2026

Etiquetas

#vue
#vue 3
#teleport
#suspense
#nuxt 3
#entrevista técnica

Compartir

Artículos relacionados