Vue 3 Teleport와 Suspense: 2026년 고급 패턴과 면접 질문
Vue 3의 Teleport와 Suspense 컴포넌트의 고급 사용 패턴을 설명합니다. defer prop, 중첩된 Suspense, 에러 핸들링, 그리고 실제 면접에서 자주 묻는 질문까지 포괄적으로 다룹니다.

Vue 3의 Teleport와 Suspense는 각각 다른 렌더링 과제를 해결합니다. Teleport는 컴포넌트를 DOM 계층 구조 외부에 배치하고, Suspense는 비동기 의존성을 조율합니다. 두 가지 모두 import가 필요 없는 내장 컴포넌트이며, 시니어 Vue 개발자 면접에서 자주 다루어지는 주제입니다.
Vue 3.5에서는 defer prop이 추가되어 컴포넌트 트리에서 나중에 렌더링되는 대상으로의 텔레포트가 가능해졌습니다. 이로써 마운트 시점에 포털 대상이 존재하지 않는 일반적인 SSR 타이밍 문제가 해결됩니다.
Teleport 기초와 defer Prop
Teleport는 렌더링된 콘텐츠를 다른 DOM 위치로 이동하면서 Vue 컴포넌트 트리를 유지합니다. 컴포넌트는 부모의 논리적 자식으로 남습니다. props, 이벤트, provide/inject 모두 정상적으로 작동합니다. Vue DevTools에서 텔레포트된 컴포넌트는 대상 요소 아래가 아닌 부모 아래에 중첩되어 표시됩니다.
<!-- 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>to prop은 CSS 선택자 문자열 또는 DOM 요소 참조를 받습니다. 마운트 시점에 대상이 존재하지 않으면 Teleport는 경고를 출력하고 아무것도 렌더링하지 않습니다. 이는 SSR과 동적 레이아웃에서 문제를 일으킵니다.
defer Prop (Vue 3.5 이상)
defer prop은 애플리케이션의 다른 부분이 마운트될 때까지 대상 해결을 기다리도록 Teleport에 지시합니다. 이를 통해 같은 컴포넌트 내에서 또는 형제 컴포넌트에서 나중에 렌더링되는 요소로 텔레포트할 수 있습니다.
<!-- App.vue -->
<template>
<Teleport defer to="#dynamic-target">
<NotificationBanner />
</Teleport>
<!-- Target rendered after the Teleport -->
<div id="dynamic-target"></div>
</template>defer가 없으면 Teleport가 마운트될 때 #dynamic-target이 존재하지 않으므로 이 코드는 실패합니다. defer를 사용하면 Vue는 같은 tick 내의 모든 컴포넌트가 마운트된 후에 대상을 해결합니다. 대상은 여전히 같은 마운트 또는 업데이트 사이클 내에서 렌더링되어야 합니다.
다중 Teleport와 조건부 렌더링
여러 Teleport 컴포넌트가 같은 요소를 대상으로 할 수 있습니다. 콘텐츠는 선언 순서대로 추가됩니다.
<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>
-->disabled prop은 텔레포트 여부를 제어합니다. 비활성화되면 콘텐츠가 인라인으로 렌더링됩니다. 이 패턴은 데스크톱에서는 모달이 오버레이로 표시되고 모바일에서는 인라인으로 렌더링되는 반응형 레이아웃을 처리합니다.
<script setup>
import { useMediaQuery } from '@vueuse/core'
const isMobile = useMediaQuery('(max-width: 768px)')
</script>
<template>
<Teleport to="body" :disabled="isMobile">
<MobileDrawer />
</Teleport>
</template>비동기 컴포넌트 조율을 위한 Suspense
Suspense는 컴포넌트 트리 전체의 비동기 의존성을 조율합니다. 하위 컴포넌트에 해결되지 않은 비동기 의존성이 있으면 Suspense는 fallback 슬롯 콘텐츠를 표시합니다. 모든 의존성이 해결되면 Suspense는 default 슬롯으로 전환됩니다.
Suspense는 Vue 3.5에서도 여전히 실험적 상태입니다. 안정 버전이 되기 전에 API가 변경될 수 있습니다. 프로덕션 환경에서 사용하려면 이 위험을 감수해야 합니다.
<!-- Dashboard.vue -->
<template>
<Suspense>
<template #default>
<DashboardContent />
</template>
<template #fallback>
<LoadingSpinner />
</template>
</Suspense>
</template>Suspense는 두 가지 유형의 비동기 의존성을 추적합니다. async setup()을 가진 컴포넌트와 defineAsyncComponent로 생성된 비동기 컴포넌트입니다.
최상위 await를 사용한 비동기 Setup
Vue 3의 script setup은 최상위 await를 지원합니다. 최상위 await를 사용하는 컴포넌트는 Suspense가 추적할 수 있는 비동기 의존성이 됩니다.
<!-- 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>이 컴포넌트는 fetch가 완료될 때까지 렌더링할 수 없습니다. 부모 Suspense 경계는 해결될 때까지 fallback을 표시합니다. Suspense 경계가 없으면 컴포넌트는 단순히 렌더링되지 않습니다.
suspensible Prop을 사용한 중첩 Suspense
복잡한 애플리케이션에는 종종 여러 비동기 경계가 있습니다. Vue 3.3에서는 suspensible prop이 도입되어 중첩된 Suspense 컴포넌트가 부모와 어떻게 상호작용하는지 제어할 수 있게 되었습니다.
<!-- 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>suspensible이 설정되면 내부 Suspense가 부모 Suspense 경계에 등록됩니다. 부모는 AsyncNavigation과 AsyncContent 모두 해결될 때까지 fallback을 숨기지 않습니다. 내부 Suspense는 여전히 로컬 경계로 작동합니다. AsyncContent가 더 오래 걸리면 AsyncNavigation이 표시된 상태에서 ContentSkeleton이 표시됩니다.
suspensible이 없으면 내부 Suspense는 독립적으로 작동합니다. 부모는 AsyncNavigation이 로드되는 동안에만 fallback을 표시하고 AsyncContent는 완전히 무시됩니다.
Vue.js / Nuxt.js 면접 준비가 되셨나요?
인터랙티브 시뮬레이터, flashcards, 기술 테스트로 연습하세요.
Teleport, Suspense, Transition 결합하기
실제 애플리케이션에서는 이러한 패턴을 결합합니다. 비동기로 콘텐츠를 로드하는 모달은 세 가지 모두의 이점을 얻습니다.
<!-- 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>중첩 순서가 중요합니다. Teleport가 Transition을 감싸고, Transition이 조건부 요소를 감싸고, 조건부 요소가 Suspense를 포함합니다. 이렇게 하면 트랜지션이 로딩 상태를 포함한 전체 모달을 애니메이션합니다.
Suspense를 통한 에러 핸들링
Suspense는 에러를 처리하지 않습니다. 실패한 비동기 의존성은 프로미스를 거부하고 에러를 컴포넌트 트리 위로 전파합니다. onErrorCaptured 라이프사이클 훅은 경계에서 이러한 에러를 잡습니다.
<!-- 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>이 패턴은 로딩 상태와 에러 상태 모두를 가진 비동기 컴포넌트 트리를 감쌉니다. 재시도 버튼은 에러를 지우고 Vue가 다시 렌더링하고 비동기 작업을 재시도하게 합니다.
Teleport와 Suspense 면접 질문
시니어 Vue 포지션에서는 이러한 패턴이 점점 더 많이 테스트됩니다. 일반적인 질문과 우수한 답변을 구별하는 포인트를 소개합니다.
Q: CSS 포지셔닝 대신 Teleport를 사용해야 하는 경우는 언제입니까?
약한 답변: "z-index가 작동하지 않을 때"
강한 답변: Teleport는 CSS로 해결할 수 없는 DOM 계층 문제를 해결합니다. overflow: hidden이나 transform을 가진 컨테이너 내부의 모달은 새로운 스태킹 컨텍스트를 생성하여 position: fixed를 깨뜨립니다. Teleport는 모달을 body로 이동시켜 Vue의 논리적 컴포넌트 트리를 유지하면서 이러한 제약에서 벗어납니다.
Q: Suspense는 각 컴포넌트의 로딩 상태와 어떻게 다릅니까?
약한 답변: "Suspense가 더 깔끔합니다"
강한 답변: Suspense는 여러 비동기 의존성을 조율합니다. 개별 로딩 상태를 가진 10개의 컴포넌트는 서로 다른 시점에 10개의 스피너를 깜빡입니다. Suspense는 10개 모두 해결될 때까지 하나의 스피너를 표시한 다음 함께 표시합니다. 이는 더 부드러운 체감 성능을 만들고, 비동기 컴포넌트가 자체 로딩 상태를 관리할 필요가 없으므로 컴포넌트 코드도 간소화됩니다.
Q: Teleport 대상이 존재하지 않으면 어떻게 됩니까?
콘텐츠가 렌더링되지 않고 Vue가 경고를 기록합니다. Vue 3.5의 defer prop은 다른 컴포넌트가 먼저 마운트되기를 기다려 이 문제를 해결합니다. defer 없이는 대상이 index.html에 있거나 자식보다 먼저 마운트되는 부모 컴포넌트에 존재해야 합니다.
Q: Suspense가 Options API와 함께 작동합니까?
예. async setup()을 가진 컴포넌트는 다른 곳에서 Composition API나 Options API를 사용하는지와 관계없이 Suspense를 트리거합니다. 비동기 경계는 setup 함수이지 컴포넌트 스타일이 아닙니다.
Nuxt 3에서의 Teleport와 Suspense
Nuxt 3는 SSR 고려 사항으로 이러한 패턴을 확장합니다. Nuxt SSR 문서에서 하이드레이션 타이밍에 대해 다룹니다.
Teleport의 경우 Nuxt는 하이드레이션 전에 대상이 존재해야 합니다. 포털 대상을 app.vue에 추가하거나 defer prop을 사용합니다.
<!-- app.vue -->
<template>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
<!-- Portal targets for SSR -->
<div id="modal-root"></div>
<div id="toast-root"></div>
</template>Suspense의 경우 Nuxt의 useFetch와 useAsyncData가 자동으로 통합됩니다. 이러한 composable을 사용하는 페이지 컴포넌트는 수동 구성 없이 Nuxt의 내장 Suspense와 함께 작동합니다. 관련 면접 준비는 SharpSkill Vue 컴포저블 모듈을 참조하십시오.
Vue 3.5를 위한 프로덕션 패턴
- index.html에 Teleport 대상 배치: Vue 코드가 실행되기 전에 존재하는 안정적인 포털 대상을 만듭니다. 이렇게 하면 타이밍 문제를 완전히 피할 수 있습니다.
- 세분화된 Suspense 경계: 독립적인 로딩 단위당 하나의 Suspense를 사용합니다. 별도의 위젯이 있는 대시보드는 빠른 위젯이 즉시 표시되도록 별도의 Suspense 경계로부터 이점을 얻습니다.
- Suspense 위에 에러 경계 배치: 항상 Suspense를 에러 핸들링으로 감쌉니다. 네트워크 오류와 API 오류가 전체 트리를 크래시시켜서는 안 됩니다.
- Transition 타이밍: Suspense와 Transition을 결합할 때 상태 변경 중 겹침을 방지하기 위해
mode="out-in"을 설정합니다.
연습을 시작하세요!
면접 시뮬레이터와 기술 테스트로 지식을 테스트하세요.
Vue.js / Nuxt.js 코드의 버그를 찾을 수 있나요
실제 코드 한 조각, 숨은 버그 하나, 하루 한 번. 계정 없이 바로 도전할 수 있습니다.

작성자
Anthony Fillion-MailletSharpSkill 창업자
10년 이상 풀스택 개발을 해왔습니다. SharpSkill을 운영하며 이곳에 게시되는 모든 내용에 책임을 집니다.
2026년 8월 28일 업데이트
태그
공유
관련 기사

2026년 Vue 3와 TypeScript: 타입 안전한 Props, Emits, 컴포저블
TypeScript로 타입 안전한 Vue 3 컴포넌트 마스터하기: 제네릭 defineProps, 튜플 defineEmits, 타입 지정 컴포저블, defineModel, InjectionKey와 면접 질문까지.

Vue 3 Composition API 완벽 가이드: 리액티비티 마스터하기
Vue 3 Composition API 실전 가이드입니다. ref, reactive, computed, watch, 컴포저블을 배워 고성능 Vue 애플리케이션을 구축하십시오.

Vue 3 Script Setup과 defineModel 2026년판: 모던 문법과 인터뷰 질문
Vue 3의 script setup 문법과 defineModel을 활용한 양방향 바인딩을 마스터합니다. 리액티브 props 구조 분해, TypeScript 패턴, Vue 인터뷰 빈출 질문을 다룹니다.