Teleport 実例:#teleports へモーダルを描画する最小構成
Nuxt で <Teleport> を使い、SSR 対応の #teleports ターゲットへモーダルを描画する実例と、任意セレクタへのクライアント限定テレポート例。
<Teleport> の to は CSS セレクタ文字列または DOM ノードを受け取ります。Nuxt では SSR 時に #teleports へのテレポートのみサポートされます。その他のターゲットへ描画したい場合は、<ClientOnly> でクライアント専用にしてください。
何を作るか
- ページ内のボタンで モーダルを開く
- モーダル本体は
<Teleport to="#teleports">で DOM の末尾(#teleports)へ描画 - 背景クリックで閉じる・閉じるボタンも用意
Nuxt の既定テンプレートには #teleports が含まれています。もし app.html をカスタムしている場合は、以下のように #teleports を必ず含めてください。
app.html
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
<head {{ HEAD_ATTRS }}>
{{ HEAD }}
</head>
<body {{ BODY_ATTRS }}>
{{ APP }}
<!-- ✅ Nuxt がテレポート先として参照する領域 -->
<div id="teleports"></div>
</body>
</html>
実装(最小)
<template>
<Teleport to="#teleports">
<div
v-if="open"
class="modal-backdrop"
@click.self="emit('update:open', false)"
>
<div class="modal-panel" role="dialog" aria-modal="true">
<header class="modal-header">
<h2 class="modal-title">テレポートされたモーダル</h2>
<button class="icon-btn" @click="emit('update:open', false)" aria-label="閉じる">×</button>
</header>
<div class="modal-body">
<p>こんにちは!この要素は <code>#teleports</code> へ描画されています。</p>
</div>
<footer class="modal-footer">
<button class="btn" @click="emit('update:open', false)">閉じる</button>
</footer>
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
const props = defineProps<{ open: boolean }>()
const emit = defineEmits<{ (e: 'update:open', v: boolean): void }>()
</script>
<style scoped>
.modal-backdrop {
position: fixed; inset: 0;
background: rgba(15, 23, 42, .55);
display: grid; place-items: center;
z-index: 50;
}
.modal-panel {
width: min(92vw, 560px);
background: #fff;
border-radius: 12px;
box-shadow: 0 20px 40px rgba(0,0,0,.2);
overflow: hidden;
}
.modal-header, .modal-footer { padding: 12px 16px; }
.modal-title { margin: 0; font-size: 18px; }
.modal-body { padding: 0 16px 16px; }
.icon-btn {
border: none; background: transparent; font-size: 18px; cursor: pointer;
}
.btn {
appearance: none;
padding: 8px 14px; border-radius: 8px;
background: #111827; color: #fff; border: 1px solid #111827;
}
.btn:hover { opacity: .9; }
</style>
別ターゲットへ「クライアント限定」テレポート
#teleports 以外に出したい時は <ClientOnly> で包めば OK(SSR は行いません)。
<template>
<div class="layout">
<aside id="sidebar" class="sidebar"><!-- サイドバー --></aside>
<main><slot /></main>
</div>
</template>
<style scoped>
.layout { display: grid; grid-template-columns: 280px 1fr; min-height: 100vh; }
.sidebar { border-right: 1px solid #e5e7eb; padding: 16px; }
</style>