Vue 3 Teleport e Suspense: Padrões Avançados e Perguntas de Entrevista 2026

Domine Teleport e Suspense no Vue 3.5 com padrões de produção, tratamento de erros e as perguntas de entrevista técnica mais frequentes para desenvolvedores Vue.

Vue 3 Teleport e Suspense padrões avançados

Vue 3 Teleport e Suspense resolvem dois desafios distintos de renderização: posicionar componentes fora de sua hierarquia DOM e coordenar dependências assíncronas. Ambos são componentes nativos que não requerem importações e aparecem frequentemente em entrevistas para desenvolvedores Vue seniores.

Atualização do Teleport no Vue 3.5

O Vue 3.5 adicionou a prop defer ao Teleport, permitindo apontar para elementos renderizados posteriormente na árvore de componentes. Isso resolve o problema comum de timing no SSR onde o alvo do portal não existe no momento da montagem.

Fundamentos do Teleport e a Prop defer

O Teleport move o conteúdo renderizado para uma localização DOM diferente enquanto preserva a árvore de componentes Vue. O componente permanece como filho lógico de seu pai: props, eventos e provide/inject funcionam normalmente. O Vue DevTools mostra o componente teleportado aninhado sob seu pai, não sob o elemento alvo.

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>

A prop to aceita uma string de seletor CSS ou uma referência de elemento DOM. Quando o alvo não existe no momento da montagem, o Teleport emite um aviso e não renderiza nada. Isso cria problemas com SSR e layouts dinâmicos.

A Prop defer (Vue 3.5+)

A prop defer instrui o Teleport a aguardar até que outras partes da aplicação sejam montadas antes de resolver o alvo. Isso permite teleportar para elementos renderizados posteriormente no mesmo componente ou em componentes irmãos.

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

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

Sem defer, este código falha porque #dynamic-target não existe quando o Teleport monta. Com defer, o Vue resolve o alvo após todos os componentes no mesmo tick terem sido montados. O alvo ainda deve ser renderizado no mesmo ciclo de montagem ou atualização.

Múltiplos Teleports e Renderização Condicional

Múltiplos componentes Teleport podem apontar para o mesmo elemento. O conteúdo é adicionado na ordem de declaração:

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>
-->

A prop disabled controla se a teleportação acontece. Quando desabilitada, o conteúdo renderiza no lugar original. Este padrão lida com layouts responsivos onde modais devem sobrepor no desktop mas renderizar inline no mobile:

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 Orquestração de Componentes Assíncronos

O Suspense coordena dependências assíncronas através de uma árvore de componentes. Quando qualquer componente descendente possui uma dependência assíncrona não resolvida, o Suspense exibe o conteúdo do slot fallback. Uma vez que todas as dependências sejam resolvidas, o Suspense alterna para o slot padrão.

Status Experimental

O Suspense permanece experimental no Vue 3.5. A API pode mudar antes de alcançar status estável. O uso em produção requer aceitar este risco.

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

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

O Suspense rastreia dois tipos de dependências assíncronas: componentes com async setup() e componentes assíncronos criados com defineAsyncComponent.

Setup Assíncrono com Top-Level Await

O Vue 3 script setup suporta top-level await. Qualquer componente usando top-level await se torna uma dependência assíncrona que o Suspense pode 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 não pode renderizar até que o fetch complete. Um boundary Suspense pai exibe o fallback até a resolução. Sem um boundary Suspense, o componente simplesmente não renderiza.

Suspense Aninhado com a Prop suspensible

Aplicações complexas frequentemente possuem múltiplos boundaries assíncronos. O Vue 3.3 introduziu a prop suspensible para controlar como componentes Suspense aninhados interagem com seus pais.

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>

Quando suspensible está definido, o Suspense interno se registra com o boundary Suspense pai. O pai aguarda tanto AsyncNavigation quanto AsyncContent resolverem antes de ocultar seu fallback. O Suspense interno ainda serve como boundary local: se AsyncContent demorar mais, ContentSkeleton é exibido enquanto AsyncNavigation permanece visível.

Sem suspensible, o Suspense interno opera independentemente. O pai mostra seu fallback apenas enquanto AsyncNavigation carrega, ignorando AsyncContent completamente.

Pronto para mandar bem nas entrevistas de Vue.js / Nuxt.js?

Pratique com nossos simuladores interativos, flashcards e testes tecnicos.

Combinando Teleport, Suspense e Transições

Aplicações reais combinam esses padrões. Um modal que carrega conteúdo assincronamente se beneficia dos três:

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>

A ordem de aninhamento importa: Teleport envolve Transition, que envolve o elemento condicional, que contém Suspense. Isso garante que a transição anime todo o modal incluindo estados de carregamento.

Tratamento de Erros com Suspense

O Suspense não trata erros. Dependências assíncronas que falham rejeitam suas promises e propagam erros para cima na árvore de componentes. O hook de ciclo de vida onErrorCaptured captura esses erros no 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 padrão envolve qualquer árvore de componentes assíncronos com estados tanto de carregamento quanto de erro. O botão de tentar novamente limpa o erro, fazendo o Vue re-renderizar e re-tentar a operação assíncrona.

Perguntas de Entrevista sobre Teleport e Suspense

Posições Vue seniores testam esses padrões cada vez mais. Perguntas comuns e o que distingue respostas fortes:

P: Quando o Teleport deve ser usado ao invés de posicionamento CSS?

Resposta fraca: "Quando z-index não funciona."

Resposta forte: O Teleport resolve problemas de hierarquia DOM que CSS não consegue resolver. Um modal dentro de um container com overflow: hidden ou transform cria um novo contexto de empilhamento, quebrando position: fixed. O Teleport move o modal para body, escapando dessas restrições enquanto preserva a árvore lógica de componentes Vue.

P: Como o Suspense difere de estados de carregamento em cada componente?

Resposta fraca: "Suspense é mais limpo."

Resposta forte: O Suspense coordena múltiplas dependências assíncronas. Dez componentes com estados de carregamento individuais mostram dez spinners em momentos diferentes. O Suspense mostra um spinner até que todos os dez resolvam, então os revela juntos. Isso cria uma performance percebida mais suave e código de componente mais simples já que componentes assíncronos não precisam gerenciar seus próprios estados de carregamento.

P: O que acontece quando um alvo Teleport não existe?

O conteúdo não renderiza e o Vue registra um aviso. A prop defer do Vue 3.5 resolve isso aguardando que outros componentes montem primeiro. Sem defer, é necessário garantir que os alvos existam no index.html ou em um componente pai que monte antes dos filhos.

P: O Suspense funciona com Options API?

Sim. Qualquer componente com async setup() aciona o Suspense, independentemente de usar Composition API ou Options API em outro lugar. A função setup é o boundary assíncrono, não o estilo do componente.

Teleport e Suspense no Nuxt 3

O Nuxt 3 estende esses padrões com considerações de SSR. A documentação do Nuxt sobre SSR cobre o timing de hidratação.

Para o Teleport, o Nuxt requer que o alvo exista antes da hidratação. É necessário adicionar alvos de portal ao app.vue ou usar a 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 o Suspense, useFetch e useAsyncData do Nuxt se integram automaticamente. Componentes de página usando esses composables funcionam com o Suspense nativo do Nuxt sem configuração manual. Consulte o módulo de composables Vue do SharpSkill para preparação relacionada a entrevistas.

Padrões de Produção para Vue 3.5

  • Alvos Teleport no index.html: Criar alvos de portal estáveis que existam antes de qualquer código Vue executar. Isso evita problemas de timing completamente.
  • Boundaries Suspense granulares: Um Suspense por unidade de carregamento independente. Um dashboard com widgets separados se beneficia de boundaries Suspense separados para que widgets rápidos apareçam imediatamente.
  • Boundaries de erro acima do Suspense: Sempre envolver Suspense com tratamento de erros. Falhas de rede e erros de API não devem crashar toda a árvore.
  • Timing de transições: Ao combinar Suspense com Transition, configurar mode="out-in" para prevenir sobreposição durante mudanças de estado.

Comece a praticar!

Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.

Desafio do dia

Você saberia encontrar o bug em Vue.js / Nuxt.js?

Um trecho real, um bug escondido, uma tentativa por dia. Sem conta para testar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador da SharpSkill

Desenvolvedor fullstack há mais de 10 anos. Dirige a SharpSkill e responde por tudo o que é publicado aqui.

Atualizado em 28 de agosto de 2026

Tags

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

Compartilhar

Artigos relacionados