[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-vue-weekly-teleport-defer-target-timing":44,"search-suggestions":60,"quiz-article-vue-weekly-teleport-defer-target-timing":107,"path-membership-vue-weekly-teleport-defer-target-timing":517},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"id":45,"slug":46,"kind":47,"title":48,"description":49,"config":50,"verticalId":5,"vertical":55,"course":52,"_count":56,"access":57,"attempts":59,"questionCount":51},"01a16761-e263-700b-b902-141793289083","vue-weekly-teleport-defer-target-timing","PRACTICE_QUIZ","Vue Teleport defer: Target Timing","Eight questions on when Vue resolves a Teleport's target, what the defer prop (3.5+) actually fixes, and what it doesn't.",{"questionCount":51,"timeLimitSec":52,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":53,"maxAttempts":52,"revealAnswers":54,"allowFlagging":18,"allowBacktracking":17},8,null,70,"AFTER_SUBMIT",{"slug":6,"name":7},{"questions":51},{"allowed":17,"reason":58},"FREE",[],[61,65,69,73,76,80,84,88,92,96,99,103],{"slug":62,"name":63,"articles":64},"webdev","Webdev",173,{"slug":66,"name":67,"articles":68},"javascript","Javascript",140,{"slug":70,"name":71,"articles":72},"frontend","Frontend",96,{"slug":74,"name":75,"articles":53},"tutorial","Tutorial",{"slug":77,"name":78,"articles":79},"css","Css",51,{"slug":81,"name":82,"articles":83},"performance","Performance",30,{"slug":85,"name":86,"articles":87},"typescript","Typescript",25,{"slug":89,"name":90,"articles":91},"react","React",20,{"slug":93,"name":94,"articles":95},"browser","Browser",18,{"slug":97,"name":98,"articles":95},"programming","Programming",{"slug":100,"name":101,"articles":102},"node","Node",15,{"slug":104,"name":105,"articles":106},"html","Html",12,{"id":108,"slug":46,"title":109,"subtitle":52,"excerpt":110,"coverUrl":111,"locale":13,"readingMinutes":112,"publishedAt":113,"viewCount":114,"likeCount":19,"commentCount":19,"author":115,"vertical":120,"topic":121,"tags":124,"_count":129,"playground":131,"body":133,"bodyMd":462,"seo":463,"translationGroupId":465,"series":466,"podcastUrl":52,"verticalId":5,"thread":508,"assessments":510,"translations":513,"quiz":515},"01a16761-e187-74dc-bd78-ecaa659b3254","Vue Teleport defer: The Target-Timing Bug Behind a Dozen Patches","Vue's Teleport defer prop took a dozen follow-up fixes after 3.5 — the target-timing bug, the Suspense interaction, plus a copy-paste cheat sheet.","\u002Fmedia\u002Fcovers\u002Fvue-weekly-teleport-defer-target-timing.png",14,"2026-11-09T15:43:15.151Z",7,{"id":116,"name":117,"username":118,"avatarUrl":52,"headline":119},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":122,"name":123},"vue","Vue",[125,126,127,128],{"slug":122,"name":123,"color":52},{"slug":66,"name":67,"color":52},{"slug":74,"name":75,"color":52},{"slug":62,"name":63,"color":52},{"assessments":130},1,{"slug":46,"title":132},"Vue Teleport defer — target-timing playground",{"blocks":134,"version":130},[135,139,144,147,156,159,162,165,176,179,183,186,191,195,198,203,206,209,212,215,218,221,224,228,231,234,238,241,244,284,287,290,298,301,308,311,315,318,322,325,328,331,334,337,341,344,348,351,354,381,385,388,391,394,397,400,403,411,414,417,420,423,426,429,432,435,438,444,447,450,453,456],{"id":136,"html":137,"type":138},"b1","\u003Cp>You add a \u003Ccode>&lt;Teleport to=&quot;#modal-root&quot;&gt;\u003C\u002Fcode> to a component, the modal works perfectly in your dev sandbox, and then it silently fails to render the first time it&#39;s mounted inside a real page layout — no error in your code, just a console warning about a target that &quot;doesn&#39;t exist,&quot; even though you can see the element sitting right there in the DOM inspector. The target exists. It just didn&#39;t exist \u003Cem>yet\u003C\u002Fem>, at the one moment Vue checked for it.\u003C\u002Fp>","paragraph",{"id":140,"html":141,"text":142,"type":143,"level":31},"b2","What You&#39;ll Learn","What You'll Learn","heading",{"id":145,"html":146,"type":138},"b3","\u003Cp>By the end of this article you&#39;ll be able to:\u003C\u002Fp>",{"id":148,"type":149,"items":150,"ordered":18},"b4","list",[151,152,153,154,155],"Explain exactly when Vue resolves a \u003Ccode>&lt;Teleport&gt;\u003C\u002Fcode> target, and why that timing — not the target&#39;s existence — is the actual bug surface","Use the \u003Ccode>defer\u003C\u002Fcode> prop correctly, and name the one case it does \u003Cstrong>not\u003C\u002Fstrong> fix","Read the real changelog history behind \u003Ccode>defer\u003C\u002Fcode>: a dozen-plus follow-up patches across Suspense, SSR, and disabled-state handling, the newest landing in the current stable release","Reproduce the classic &quot;target renders after the Teleport&quot; failure and fix it live","Apply the right defensive pattern so your own Teleport usage doesn&#39;t depend on component mount order",{"id":157,"html":158,"text":158,"type":143,"level":31},"b5","Who This Is For",{"id":160,"html":161,"type":138},"b6","\u003Cp>You&#39;ve used \u003Ccode>&lt;Teleport&gt;\u003C\u002Fcode> to render a modal, a tooltip, or a toast into a fixed DOM location and it has mostly just worked. This article assumes that baseline. If component mount timing in general — when the DOM actually exists relative to a component&#39;s lifecycle — is still fuzzy, \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-nexttick-explained-how-dom-updates-are-batched-3gnp\">Vue nextTick: How DOM Updates Are Batched\u003C\u002Fa> covers the adjacent queue this article&#39;s timing model sits next to. Useful background, not required reading.\u003C\u002Fp>",{"id":163,"html":164,"text":164,"type":143,"level":31},"b7","Table of Contents",{"id":166,"type":149,"items":167,"ordered":18},"b8",[168,169,170,171,172,173,174,175],"\u003Ca href=\"#the-problem-a-teleport-target-that-doesnt-exist\">The Problem: A Teleport Target That &quot;Doesn&#39;t Exist&quot;\u003C\u002Fa>","\u003Ca href=\"#the-mental-model-teleport-resolves-its-target-once-synchronously\">The Mental Model: Teleport Resolves Its Target Once, Synchronously\u003C\u002Fa>","\u003Ca href=\"#building-it-up-from-the-failure-to-defer\">Building It Up: From the Failure to defer\u003C\u002Fa>","\u003Ca href=\"#edge-cases-and-gotchas\">Edge Cases and Gotchas\u003C\u002Fa>","\u003Ca href=\"#best-practices\">Best Practices\u003C\u002Fa>","\u003Ca href=\"#faq\">FAQ\u003C\u002Fa>","\u003Ca href=\"#cheat-sheet\">Cheat Sheet\u003C\u002Fa>","\u003Ca href=\"#key-takeaways\">Key Takeaways\u003C\u002Fa>",{"id":177,"html":178,"type":138},"b9","\u003Cp>This is written and tested against \u003Cstrong>Vue 3.5.44\u003C\u002Fstrong> — the release carrying the most recent Teleport-specific fix. (npm&#39;s \u003Ccode>latest\u003C\u002Fcode> tag has since moved to 3.5.45, verified November 2026, which ships no Teleport changes, so nothing below is stale.) Vue 3.6 is still on the \u003Ccode>rc\u003C\u002Fcode> tag (3.6.0-rc.14 at the time of writing) and hasn&#39;t reached stable, so nothing here depends on it. Everything below — the \u003Ccode>defer\u003C\u002Fcode> prop, the mount-order behavior, and every patch referenced — ships in 3.5.x today.\u003C\u002Fp>",{"id":180,"html":181,"text":182,"type":143,"level":31},"b10","The Problem: A Teleport Target That &quot;Doesn&#39;t Exist&quot;","The Problem: A Teleport Target That \"Doesn't Exist\"",{"id":184,"html":185,"type":138},"b11","\u003Cp>Here&#39;s the setup almost everyone hits once: a layout component renders a modal-trigger area before it renders the container the modal is supposed to teleport into.\u003C\u002Fp>",{"id":187,"code":188,"type":189,"language":122,"highlight":190},"b12","\u003C!-- AppShell.vue -->\n\u003Ctemplate>\n  \u003CHeader \u002F>\n  \u003CModalTrigger \u002F>\n  \u003C!-- the teleport target is defined further down the tree -->\n  \u003Cdiv id=\"modal-root\">\u003C\u002Fdiv>\n\u003C\u002Ftemplate>","code",[],{"id":192,"code":193,"type":189,"language":122,"highlight":194},"b13","\u003C!-- ModalTrigger.vue -->\n\u003Ctemplate>\n  \u003Cbutton @click=\"open = true\">Open modal\u003C\u002Fbutton>\n  \u003CTeleport to=\"#modal-root\">\n    \u003Cdiv v-if=\"open\" class=\"modal\">Hello from a teleported modal\u003C\u002Fdiv>\n  \u003C\u002FTeleport>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport { ref } from 'vue'\nconst open = ref(false)\n\u003C\u002Fscript>",[],{"id":196,"html":197,"type":138},"b14","\u003Cp>This looks completely reasonable, and in a plain top-to-bottom static template like the one above it actually works, because Vue builds the whole DOM tree — \u003Ccode>#modal-root\u003C\u002Fcode> included — before anything is &quot;open.&quot; But swap \u003Ccode>&lt;div id=&quot;modal-root&quot;&gt;\u003C\u002Fcode> for something that only exists once a sibling component finishes its own async setup, or move \u003Ccode>ModalTrigger\u003C\u002Fcode> into a component that mounts earlier than the one defining \u003Ccode>#modal-root\u003C\u002Fcode> (a common shape with portals, layout slots, or anything behind \u003Ccode>v-if\u003C\u002Fcode>), and you get this in the console the moment \u003Ccode>ModalTrigger\u003C\u002Fcode> mounts:\u003C\u002Fp>",{"id":199,"code":200,"type":189,"language":201,"highlight":202},"b15","[Vue warn]: Failed to locate Teleport target with selector \"#modal-root\". Note the target\nelement must exist before the component is mounted - i.e. the target cannot be rendered by\nthe component itself, and ideally should be outside of the entire Vue component tree.\n[Vue warn]: Invalid Teleport target on mount: null (object)","plain",[],{"id":204,"html":205,"type":138},"b16","\u003Cp>Nothing renders. Not an error that stops your app — a warning, and the teleported content just silently doesn&#39;t appear. If you&#39;ve ever debugged &quot;the modal content is in my Vue Devtools component tree but not in the actual DOM,&quot; this is almost always why: Vue looked for the target, didn&#39;t find it yet, and gave up.\u003C\u002Fp>",{"id":207,"html":208,"text":208,"type":143,"level":31},"b17","The Mental Model: Teleport Resolves Its Target Once, Synchronously",{"id":210,"html":211,"type":138},"b18","\u003Cp>Here&#39;s the detail that explains both the bug and the fix: \u003Cstrong>\u003Ccode>&lt;Teleport&gt;\u003C\u002Fcode> doesn&#39;t continuously watch for its target to appear.\u003C\u002Fstrong> It resolves the target selector to a real DOM node exactly once, at a specific point in its own mount process, and if that lookup fails, it doesn&#39;t retry on its own.\u003C\u002Fp>",{"id":213,"html":214,"type":138},"b19","\u003Cp>\u003Cstrong>The mental model:\u003C\u002Fstrong> every component in your tree mounts in a fixed order — roughly, a parent sets up, then its children mount in template order, then the parent&#39;s own mount finishes. A \u003Ccode>&lt;Teleport&gt;\u003C\u002Fcode> is just one more thing that mounts at its position in that order. When \u003Ccode>ModalTrigger\u003C\u002Fcode> mounts, Vue runs \u003Ccode>querySelector(&#39;#modal-root&#39;)\u003C\u002Fcode> right then, synchronously, as part of mounting the Teleport. If \u003Ccode>#modal-root\u003C\u002Fcode> is a sibling that comes \u003Cstrong>later\u003C\u002Fstrong> in \u003Ccode>AppShell\u003C\u002Fcode>&#39;s template, it may not exist in the DOM yet at that exact instant — \u003Ccode>AppShell\u003C\u002Fcode> is still partway through mounting its own children. The failure isn&#39;t about whether the target element exists in your app at all; it&#39;s about whether it exists in the DOM \u003Cstrong>at the single moment Vue checks.\u003C\u002Fstrong>\u003C\u002Fp>",{"id":216,"html":217,"type":138},"b20","\u003Cp>This is also why the fix Vue shipped for this — the \u003Ccode>defer\u003C\u002Fcode> prop, added in 3.5 — is described in the official docs as working &quot;similarly to the \u003Ccode>mounted\u003C\u002Fcode> lifecycle hook.&quot; A normal Teleport resolves its target during the synchronous mount pass, before any component&#39;s \u003Ccode>mounted\u003C\u002Fcode> hook has run. A \u003Cstrong>deferred\u003C\u002Fstrong> Teleport delays that same lookup until after the current render cycle — until the rest of the tree being mounted in this pass has had its chance to exist in the DOM, much like code in \u003Ccode>mounted()\u003C\u002Fcode> runs after the whole subtree is attached.\u003C\u002Fp>",{"id":219,"html":220,"text":220,"type":143,"level":31},"b21","Building It Up: From the Failure to defer",{"id":222,"html":223,"type":138},"b22","\u003Cp>\u003Cstrong>Stage 1 — the target exists, but too late.\u003C\u002Fstrong> This is the exact shape from the problem section:\u003C\u002Fp>",{"id":225,"code":226,"type":189,"language":122,"highlight":227},"b23","\u003CTeleport to=\"#modal-root\">\n  \u003Cdiv v-if=\"open\" class=\"modal\">Hello\u003C\u002Fdiv>\n\u003C\u002FTeleport>\n\u003C!-- #modal-root is defined in a sibling that mounts AFTER this one -->",[],{"id":229,"html":230,"type":138},"b24","\u003Cp>Without any extra prop, Vue resolves \u003Ccode>#modal-root\u003C\u002Fcode> synchronously, right now, as part of mounting this Teleport. If the sibling that defines \u003Ccode>#modal-root\u003C\u002Fcode> hasn&#39;t mounted yet, the lookup fails and you get the warning from the problem section. \u003Cstrong>Key concept:\u003C\u002Fstrong> the content you&#39;re teleporting (\u003Ccode>open\u003C\u002Fcode>) being falsy doesn&#39;t matter — the target lookup happens at mount time regardless of whether anything is currently being teleported.\u003C\u002Fp>",{"id":232,"html":233,"type":138},"b25","\u003Cp>\u003Cstrong>Stage 2 — add \u003Ccode>defer\u003C\u002Fcode>.\u003C\u002Fstrong> This is the fix, available since Vue 3.5:\u003C\u002Fp>",{"id":235,"code":236,"type":189,"language":122,"highlight":237},"b26","\u003CTeleport defer to=\"#modal-root\">\n  \u003Cdiv v-if=\"open\" class=\"modal\">Hello\u003C\u002Fdiv>\n\u003C\u002FTeleport>",[],{"id":239,"html":240,"type":138},"b27","\u003Cp>With \u003Ccode>defer\u003C\u002Fcode>, Vue doesn&#39;t give up on the first failed lookup. It waits until the current render cycle finishes — after every sibling in this same mount pass has had a chance to render — and tries the selector again. In the \u003Ccode>AppShell\u003C\u002Fcode> example, by the time that second lookup runs, \u003Ccode>#modal-root\u003C\u002Fcode> exists, because \u003Ccode>AppShell\u003C\u002Fcode>&#39;s full template has finished mounting. \u003Cstrong>Key concept:\u003C\u002Fstrong> \u003Ccode>defer\u003C\u002Fcode> doesn&#39;t make Teleport reactive to the target appearing at \u003Cem>any\u003C\u002Fem> future point — it buys exactly one extra chance, timed to land after the surrounding tree&#39;s initial mount.\u003C\u002Fp>",{"id":242,"html":243,"type":138},"b28","\u003Cp>\u003Cstrong>Stage 3 — why that one extra chance needed its own bug-fix history.\u003C\u002Fstrong> \u003Ccode>defer\u003C\u002Fcode> sounds like it should have been a clean, one-PR feature: delay a lookup, try again. In practice, &quot;after the current render cycle&quot; interacts with nearly every other thing Vue schedules around mounting, and getting it right took far more than one pass. Reading straight through the \u003Ccode>vuejs\u002Fcore\u003C\u002Fcode> changelog for \u003Ccode>Teleport\u003C\u002Fcode>-related fixes since \u003Ccode>defer\u003C\u002Fcode> shipped:\u003C\u002Fp>",{"id":245,"head":246,"rows":249,"type":283},"b29",[247,248],"Version","Fixes",[250,253,256,259,262,265,268,271,274,277,280],[251,252],"3.5.12","A disabled Teleport combined with \u003Ccode>updateCssVars\u003C\u002Fcode> (\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F12113\">#12113\u003C\u002Fa>)",[254,255],"3.5.13","Deferred Teleport updates arriving \u003Cem>before\u003C\u002Fem> \u003Ccode>mounted\u003C\u002Fcode> (\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F12168\">#12168\u003C\u002Fa>)",[257,258],"3.5.15","Deferred Teleport updates arriving both before \u003Cstrong>and\u003C\u002Fstrong> after mount (\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F13350\">#13350\u003C\u002Fa>)",[260,261],"3.5.21","Hydrating a disabled Teleport with an undefined target (\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F11235\">#11235\u003C\u002Fa>)",[263,264],"3.5.22","Multiple Teleports mounting correctly inside a custom element with \u003Ccode>shadowRoot: false\u003C\u002Fcode> (\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F13900\">#13900\u003C\u002Fa>)",[266,267],"3.5.28","Handling of the disabled Teleport&#39;s target anchor (\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F14417\">#14417\u003C\u002Fa>)",[269,270],"3.5.31","Deferring Teleport mount\u002Fupdate until \u003Ccode>Suspense\u003C\u002Fcode> resolves (\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F8619\">#8619\u003C\u002Fa>)",[272,273],"3.5.32","Teleport updates that arrive before its own deferred mount completes (\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F14642\">#14642\u003C\u002Fa>)",[275,276],"3.5.33","Stop moving a Teleport&#39;s children when the Teleport itself isn&#39;t mounted (\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F14702\">#14702\u003C\u002Fa>)",[278,279],"3.5.39","Nested async Teleport content resolving correctly under SSR (\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F9431\">#9431\u003C\u002Fa>)",[281,282],"3.5.44","Child-node ordering around \u003Ccode>targetStart\u003C\u002Fcode>\u002F\u003Ccode>targetAnchor\u003C\u002Fcode> (\u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F15818\">#15818\u003C\u002Fa>)","table",{"id":285,"html":286,"type":138},"b30","\u003Cp>Eleven patches, across more than a year of 3.5.x point releases, and the most recent one landed in the version this article verifies against. \u003Cstrong>Key concept:\u003C\u002Fstrong> this isn&#39;t a sign that \u003Ccode>defer\u003C\u002Fcode> is fragile or that you should avoid it — it&#39;s a sign of how many real, independent timing paths a single &quot;try again later&quot; feature has to account for: \u003Ccode>Suspense\u003C\u002Fcode> boundaries that resolve asynchronously, SSR hydration reconciling server-rendered markup against a client mount, disabled Teleports that have no target to resolve at all, and nested component trees unmounting mid-flight. Each row above is a real, reproducible scenario someone hit in production and filed. The practical upshot: \u003Ccode>defer\u003C\u002Fcode> genuinely fixes the ordering problem from Stage 1, and by 3.5.44 the surrounding edge cases around it are, as far as the project&#39;s own issue tracker shows, settled — but it took iterating against real usage, not a single clean implementation, to get there.\u003C\u002Fp>",{"id":288,"html":289,"text":289,"type":143,"level":31},"b31","Edge Cases and Gotchas",{"id":291,"type":149,"items":292,"ordered":18},"b32",[293,294,295,296,297],"\u003Cstrong>\u003Ccode>defer\u003C\u002Fcode> only works within the same mount\u002Fupdate cycle.\u003C\u002Fstrong> It is not a poll, a \u003Ccode>MutationObserver\u003C\u002Fcode>, or a retry-forever mechanism. If the target element is added a tick later — inside a \u003Ccode>setTimeout\u003C\u002Fcode>, after an unrelated async operation, or from code outside Vue entirely — \u003Ccode>defer\u003C\u002Fcode> will not find it, and you get the exact same &quot;Invalid Teleport target&quot; warning as without \u003Ccode>defer\u003C\u002Fcode>. The fix is for \u003Cstrong>ordering within one render\u003C\u002Fstrong>, not for a target that doesn&#39;t exist yet at all.","\u003Cstrong>\u003Ccode>defer\u003C\u002Fcode> plus \u003Ccode>Suspense\u003C\u002Fcode> needed a dedicated fix.\u003C\u002Fstrong> If your Teleport sits inside a component wrapped in \u003Ccode>&lt;Suspense&gt;\u003C\u002Fcode>, the mount\u002Fupdate itself has to wait for the async dependency to resolve before \u003Ccode>defer\u003C\u002Fcode>&#39;s &quot;try again&quot; logic even makes sense — that&#39;s specifically what 3.5.31 (PR \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F8619\">#8619\u003C\u002Fa>) addressed. If you&#39;re on an older 3.5.x patch and see a deferred Teleport inside \u003Ccode>Suspense\u003C\u002Fcode> behave inconsistently, upgrading is the first thing to try before debugging your own code.","\u003Cstrong>A \u003Ccode>:disabled\u003C\u002Fcode> Teleport still needs a valid target expression, even if it never activates.\u003C\u002Fstrong> Several of the patches above (3.5.12, 3.5.21, 3.5.28) are specifically about the disabled state interacting badly with target resolution or hydration. Don&#39;t assume \u003Ccode>disabled\u003C\u002Fcode> sidesteps target-lookup bugs entirely; test the disabled path too if your Teleport toggles \u003Ccode>disabled\u003C\u002Fcode> dynamically.","\u003Cstrong>Multiple Teleports targeting the same element render in mount order, not template order.\u003C\u002Fstrong> If two different components each teleport into \u003Ccode>#modal-root\u003C\u002Fcode>, whichever one mounts first lands first in the DOM. This isn&#39;t something \u003Ccode>defer\u003C\u002Fcode> changes — it&#39;s an orthogonal fact worth knowing if you ever stack more than one Teleport onto a shared container and the visual order looks unexpected.","\u003Cstrong>SSR has its own resolution path, not the same as client-side mount.\u003C\u002Fstrong> The 3.5.39 fix (PR \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F9431\">#9431\u003C\u002Fa>) was specifically about nested async Teleport content during server rendering — a separate code path from the browser&#39;s synchronous \u003Ccode>querySelector\u003C\u002Fcode> lookup described in the mental model above. If you&#39;re teleporting content from a component that&#39;s itself inside an async \u003Ccode>&lt;Suspense&gt;\u003C\u002Fcode> boundary during SSR, verify on 3.5.39 or later.",{"id":299,"html":300,"text":300,"type":143,"level":31},"b33","Best Practices",{"id":302,"type":149,"items":303,"ordered":18},"b34",[304,305,306,307],"\u003Cstrong>Default to \u003Ccode>defer\u003C\u002Fcode> whenever the target might be a sibling rendered later in the same tree\u003C\u002Fstrong>, or whenever you&#39;re not in full control of render order (layout slots, portals provided by a parent, conditionally-mounted containers). It costs nothing when the target already exists early, and it&#39;s the documented fix for exactly this failure mode.","\u003Cstrong>Don&#39;t reach for \u003Ccode>defer\u003C\u002Fcode> as a substitute for guaranteeing the target exists.\u003C\u002Fstrong> If the container element is itself behind an async condition that might resolve \u003Cem>after\u003C\u002Fem> the current mount pass — a fetched layout, a lazily-loaded route — \u003Ccode>defer\u003C\u002Fcode> won&#39;t help. Either hoist the target markup above the async boundary, or hold off rendering the Teleport (\u003Ccode>v-if\u003C\u002Fcode>) until you know the target is present.","\u003Cstrong>Stay on a recent 3.5.x patch.\u003C\u002Fstrong> Given how many of the fixes above are genuinely separate scenarios rather than regressions of each other, an old 3.5.x pin (anything before .39, in particular) is leaving real, already-fixed Teleport bugs on the table for no reason.","\u003Cstrong>Test the \u003Ccode>:disabled\u003C\u002Fcode> toggle path explicitly\u003C\u002Fstrong>, not just the &quot;always enabled&quot; happy path, if your Teleport&#39;s \u003Ccode>disabled\u003C\u002Fcode> prop is ever reactive. That&#39;s where several of the subtler fixes above lived.",{"id":309,"html":310,"text":310,"type":143,"level":31},"b35","FAQ",{"id":312,"html":313,"text":314,"type":143,"level":43},"b36","Does \u003Ccode>defer\u003C\u002Fcode> fix every &quot;Teleport target not found&quot; warning?","Does defer fix every \"Teleport target not found\" warning?",{"id":316,"html":317,"type":138},"b37","\u003Cp>No. It fixes the specific case where the target exists somewhere in the tree being mounted in the same render pass, just later than the Teleport itself. If the target is added by unrelated code after that pass completes — a later \u003Ccode>setTimeout\u003C\u002Fcode>, a separate async fetch that isn&#39;t part of the same mount — \u003Ccode>defer\u003C\u002Fcode> still fails the same way a non-deferred Teleport would.\u003C\u002Fp>",{"id":319,"html":320,"text":321,"type":143,"level":43},"b38","What Vue version added \u003Ccode>defer\u003C\u002Fcode>?","What Vue version added defer?",{"id":323,"html":324,"type":138},"b39","\u003Cp>Vue 3.5. It&#39;s documented in the Teleport guide and the built-in components API reference as a 3.5+ feature.\u003C\u002Fp>",{"id":326,"html":327,"text":327,"type":143,"level":43},"b40","Why did a feature this small need a dozen follow-up patches?",{"id":329,"html":330,"type":138},"b41","\u003Cp>Because &quot;retry the target lookup once, later&quot; touches nearly every other thing Vue schedules around mounting: \u003Ccode>Suspense\u003C\u002Fcode> resolution, SSR hydration, the disabled state, nested unmounts, and multi-Teleport ordering inside shadow DOM. Each patch in the table above fixed one specific, independently-reported interaction — not the same bug reappearing.\u003C\u002Fp>",{"id":332,"html":333,"text":333,"type":143,"level":43},"b42","Is Vue 3.6 going to change how Teleport resolves its target?",{"id":335,"html":336,"type":138},"b43","\u003Cp>Nothing in the public 3.6 release-candidate notes changes Teleport&#39;s resolution model as of this writing (3.6.0-rc.14). This article covers 3.5.x behavior, which is what every published 3.6 pre-release so far still runs for this feature.\u003C\u002Fp>",{"id":338,"html":339,"text":340,"type":143,"level":43},"b44","Should I just wrap every Teleport in \u003Ccode>defer\u003C\u002Fcode> by default?","Should I just wrap every Teleport in defer by default?",{"id":342,"html":343,"type":138},"b45","\u003Cp>It&#39;s a reasonable default for any Teleport whose target isn&#39;t guaranteed to exist before the Teleport itself mounts, and the cost of adding it when it isn&#39;t strictly needed is effectively zero. It is not, however, a reason to stop thinking about where the target actually lives.\u003C\u002Fp>",{"id":345,"html":346,"text":347,"type":143,"level":43},"b46","Does \u003Ccode>defer\u003C\u002Fcode> interact with batching the way \u003Ccode>watch\u003C\u002Fcode> or \u003Ccode>computed\u003C\u002Fcode> do?","Does defer interact with batching the way watch or computed do?",{"id":349,"html":350,"type":138},"b47","\u003Cp>No — \u003Ccode>defer\u003C\u002Fcode>&#39;s one-time retry is a mount\u002Frender-cycle concern specific to Teleport&#39;s target resolution, not the shared reactivity notify buffer that \u003Ccode>ref\u003C\u002Fcode>, \u003Ccode>computed\u003C\u002Fcode>, and \u003Ccode>watch\u003C\u002Fcode> all queue through. If that separate mechanism is unfamiliar, \u003Ca href=\"https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-36-when-a-watcher-throws-your-other-watchers-stall-25ge\">Vue&#39;s reactivity batch and flush() loop\u003C\u002Fa> covers it — useful context, not required here.\u003C\u002Fp>",{"id":352,"html":353,"text":353,"type":143,"level":31},"b48","Cheat Sheet",{"id":355,"head":356,"rows":359,"type":283},"b49",[357,358],"Situation","What to do",[360,363,366,369,372,375,378],[361,362],"Target is a static element always present before the Teleport mounts","No \u003Ccode>defer\u003C\u002Fcode> needed",[364,365],"Target is a sibling that renders \u003Cstrong>later\u003C\u002Fstrong> in the same mount pass","Add \u003Ccode>defer\u003C\u002Fcode>",[367,368],"Target may not exist until an unrelated async event later","\u003Ccode>defer\u003C\u002Fcode> won&#39;t help — gate the Teleport with \u003Ccode>v-if\u003C\u002Fcode> instead",[370,371],"Teleport is inside \u003Ccode>&lt;Suspense&gt;\u003C\u002Fcode>","Needs 3.5.31+ for correct \u003Ccode>defer\u003C\u002Fcode> interaction",[373,374],"Teleport ever toggles \u003Ccode>:disabled\u003C\u002Fcode>","Verify on 3.5.28+ for correct anchor handling",[376,377],"Teleporting during SSR with nested async content","Needs 3.5.39+",[379,380],"Multiple Teleports share one target","They render in \u003Cstrong>mount order\u003C\u002Fstrong>, not template order",{"id":382,"code":383,"type":189,"language":122,"highlight":384},"b50","\u003C!-- The safe default when you're not 100% sure the target exists yet -->\n\u003CTeleport defer to=\"#modal-root\">\n  \u003Cdiv v-if=\"open\" class=\"modal\">…\u003C\u002Fdiv>\n\u003C\u002FTeleport>",[],{"id":386,"html":387,"type":138},"b51","\u003C!-- playground:start -->",{"id":389,"html":390,"text":390,"type":143,"level":31},"b52","🎮 Try it yourself",{"id":392,"html":393,"type":138},"b53","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-teleport-defer-target-timing\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":395,"html":396,"type":138},"b54","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":398,"html":399,"type":138},"b55","\u003C!-- playground:end -->",{"id":401,"html":402,"text":402,"type":143,"level":31},"b56","Key Takeaways",{"id":404,"type":149,"items":405,"ordered":18},"b57",[406,407,408,409,410],"\u003Ccode>&lt;Teleport&gt;\u003C\u002Fcode> resolves its target selector exactly once, synchronously, at mount time — it does not watch for the target to appear later.","The classic failure is a target that&#39;s a sibling rendered \u003Cstrong>after\u003C\u002Fstrong> the Teleport in the same component tree; \u003Ccode>defer\u003C\u002Fcode> (Vue 3.5+) fixes this by retrying the lookup once, after the current render cycle, similar in timing to the \u003Ccode>mounted\u003C\u002Fcode> hook.","\u003Ccode>defer\u003C\u002Fcode> is not a general &quot;wait until the target exists&quot; mechanism — it only covers the same mount\u002Fupdate cycle, not a target added later by unrelated code.","Getting \u003Ccode>defer\u003C\u002Fcode> right across Suspense, SSR, the disabled state, and multi-Teleport ordering took more than a dozen separate patches through 3.5.x, the most recent in 3.5.44 — treat an old 3.5 pin as leaving real fixes unclaimed.","Multiple Teleports sharing one target land in mount order, not template order — an orthogonal fact worth knowing even when \u003Ccode>defer\u003C\u002Fcode> isn&#39;t in play.",{"id":412,"html":413,"type":138},"b58","\u003C!-- quiz:start -->",{"id":415,"html":416,"text":416,"type":143,"level":31},"b59","🧠 Test yourself",{"id":418,"html":419,"type":138},"b60","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-teleport-defer-target-timing\u002Fquiz\">Take the 8-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":421,"html":422,"type":138},"b61","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":424,"html":425,"type":138},"b62","\u003C!-- quiz:end -->",{"id":427,"html":428,"type":138},"b63","\u003Cp>A target that exists in your app and a target that exists in the DOM \u003Cem>at the exact moment Vue looks for it\u003C\u002Fem> are two different claims — \u003Ccode>defer\u003C\u002Fcode> closes the gap between them for one common case, not for all of them. The next time a Teleport &quot;just doesn&#39;t show up,&quot; check mount order before you check your own code.\u003C\u002Fp>",{"id":430,"html":431,"type":138},"b64","\u003Cp>Have you hit the &quot;Invalid Teleport target&quot; warning in a layout you were sure was correct? Drop what finally fixed it in the comments.\u003C\u002Fp>",{"id":433,"html":434,"type":138},"b65","\u003C!-- related:start -->",{"id":436,"html":437,"text":437,"type":143,"level":31},"b66","📚 Read next",{"id":439,"type":149,"items":440,"ordered":18},"b67",[441,442,443],"\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-composables-shared-state-trap\">Vue Composables: The Shared State Trap (+ Cheat Sheet)\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-definemodel-v-model-contract\">Vue defineModel: The v-model Contract, Explained\u003C\u002Fa>","\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-watch-vs-watcheffect\">Vue watch vs watchEffect: The Complete Guide\u003C\u002Fa>",{"id":445,"html":446,"type":138},"b68","\u003C!-- related:end -->",{"id":448,"type":449},"b69","divider",{"id":451,"html":452,"type":138},"b70","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":454,"html":455,"type":138},"b71","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":457,"type":149,"items":458,"ordered":18},"b72",[459,460,461],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","You add a `\u003CTeleport to=\"#modal-root\">` to a component, the modal works perfectly in your dev sandbox, and then it silently fails to render the first time it's mounted inside a real page layout — no error in your code, just a console warning about a target that \"doesn't exist,\" even though you can see the element sitting right there in the DOM inspector. The target exists. It just didn't exist *yet*, at the one moment Vue checked for it.\n\n## What You'll Learn\n\nBy the end of this article you'll be able to:\n\n- Explain exactly when Vue resolves a `\u003CTeleport>` target, and why that timing — not the target's existence — is the actual bug surface\n- Use the `defer` prop correctly, and name the one case it does **not** fix\n- Read the real changelog history behind `defer`: a dozen-plus follow-up patches across Suspense, SSR, and disabled-state handling, the newest landing in the current stable release\n- Reproduce the classic \"target renders after the Teleport\" failure and fix it live\n- Apply the right defensive pattern so your own Teleport usage doesn't depend on component mount order\n\n## Who This Is For\n\nYou've used `\u003CTeleport>` to render a modal, a tooltip, or a toast into a fixed DOM location and it has mostly just worked. This article assumes that baseline. If component mount timing in general — when the DOM actually exists relative to a component's lifecycle — is still fuzzy, [Vue nextTick: How DOM Updates Are Batched](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-nexttick-explained-how-dom-updates-are-batched-3gnp) covers the adjacent queue this article's timing model sits next to. Useful background, not required reading.\n\n## Table of Contents\n\n- [The Problem: A Teleport Target That \"Doesn't Exist\"](#the-problem-a-teleport-target-that-doesnt-exist)\n- [The Mental Model: Teleport Resolves Its Target Once, Synchronously](#the-mental-model-teleport-resolves-its-target-once-synchronously)\n- [Building It Up: From the Failure to defer](#building-it-up-from-the-failure-to-defer)\n- [Edge Cases and Gotchas](#edge-cases-and-gotchas)\n- [Best Practices](#best-practices)\n- [FAQ](#faq)\n- [Cheat Sheet](#cheat-sheet)\n- [Key Takeaways](#key-takeaways)\n\nThis is written and tested against **Vue 3.5.44** — the release carrying the most recent Teleport-specific fix. (npm's `latest` tag has since moved to 3.5.45, verified November 2026, which ships no Teleport changes, so nothing below is stale.) Vue 3.6 is still on the `rc` tag (3.6.0-rc.14 at the time of writing) and hasn't reached stable, so nothing here depends on it. Everything below — the `defer` prop, the mount-order behavior, and every patch referenced — ships in 3.5.x today.\n\n## The Problem: A Teleport Target That \"Doesn't Exist\"\n\nHere's the setup almost everyone hits once: a layout component renders a modal-trigger area before it renders the container the modal is supposed to teleport into.\n\n```vue\n\u003C!-- AppShell.vue -->\n\u003Ctemplate>\n  \u003CHeader \u002F>\n  \u003CModalTrigger \u002F>\n  \u003C!-- the teleport target is defined further down the tree -->\n  \u003Cdiv id=\"modal-root\">\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n```\n\n```vue\n\u003C!-- ModalTrigger.vue -->\n\u003Ctemplate>\n  \u003Cbutton @click=\"open = true\">Open modal\u003C\u002Fbutton>\n  \u003CTeleport to=\"#modal-root\">\n    \u003Cdiv v-if=\"open\" class=\"modal\">Hello from a teleported modal\u003C\u002Fdiv>\n  \u003C\u002FTeleport>\n\u003C\u002Ftemplate>\n\n\u003Cscript setup>\nimport { ref } from 'vue'\nconst open = ref(false)\n\u003C\u002Fscript>\n```\n\nThis looks completely reasonable, and in a plain top-to-bottom static template like the one above it actually works, because Vue builds the whole DOM tree — `#modal-root` included — before anything is \"open.\" But swap `\u003Cdiv id=\"modal-root\">` for something that only exists once a sibling component finishes its own async setup, or move `ModalTrigger` into a component that mounts earlier than the one defining `#modal-root` (a common shape with portals, layout slots, or anything behind `v-if`), and you get this in the console the moment `ModalTrigger` mounts:\n\n```\n[Vue warn]: Failed to locate Teleport target with selector \"#modal-root\". Note the target\nelement must exist before the component is mounted - i.e. the target cannot be rendered by\nthe component itself, and ideally should be outside of the entire Vue component tree.\n[Vue warn]: Invalid Teleport target on mount: null (object)\n```\n\nNothing renders. Not an error that stops your app — a warning, and the teleported content just silently doesn't appear. If you've ever debugged \"the modal content is in my Vue Devtools component tree but not in the actual DOM,\" this is almost always why: Vue looked for the target, didn't find it yet, and gave up.\n\n## The Mental Model: Teleport Resolves Its Target Once, Synchronously\n\nHere's the detail that explains both the bug and the fix: **`\u003CTeleport>` doesn't continuously watch for its target to appear.** It resolves the target selector to a real DOM node exactly once, at a specific point in its own mount process, and if that lookup fails, it doesn't retry on its own.\n\n**The mental model:** every component in your tree mounts in a fixed order — roughly, a parent sets up, then its children mount in template order, then the parent's own mount finishes. A `\u003CTeleport>` is just one more thing that mounts at its position in that order. When `ModalTrigger` mounts, Vue runs `querySelector('#modal-root')` right then, synchronously, as part of mounting the Teleport. If `#modal-root` is a sibling that comes **later** in `AppShell`'s template, it may not exist in the DOM yet at that exact instant — `AppShell` is still partway through mounting its own children. The failure isn't about whether the target element exists in your app at all; it's about whether it exists in the DOM **at the single moment Vue checks.**\n\nThis is also why the fix Vue shipped for this — the `defer` prop, added in 3.5 — is described in the official docs as working \"similarly to the `mounted` lifecycle hook.\" A normal Teleport resolves its target during the synchronous mount pass, before any component's `mounted` hook has run. A **deferred** Teleport delays that same lookup until after the current render cycle — until the rest of the tree being mounted in this pass has had its chance to exist in the DOM, much like code in `mounted()` runs after the whole subtree is attached.\n\n## Building It Up: From the Failure to defer\n\n**Stage 1 — the target exists, but too late.** This is the exact shape from the problem section:\n\n```vue\n\u003CTeleport to=\"#modal-root\">\n  \u003Cdiv v-if=\"open\" class=\"modal\">Hello\u003C\u002Fdiv>\n\u003C\u002FTeleport>\n\u003C!-- #modal-root is defined in a sibling that mounts AFTER this one -->\n```\n\nWithout any extra prop, Vue resolves `#modal-root` synchronously, right now, as part of mounting this Teleport. If the sibling that defines `#modal-root` hasn't mounted yet, the lookup fails and you get the warning from the problem section. **Key concept:** the content you're teleporting (`open`) being falsy doesn't matter — the target lookup happens at mount time regardless of whether anything is currently being teleported.\n\n**Stage 2 — add `defer`.** This is the fix, available since Vue 3.5:\n\n```vue\n\u003CTeleport defer to=\"#modal-root\">\n  \u003Cdiv v-if=\"open\" class=\"modal\">Hello\u003C\u002Fdiv>\n\u003C\u002FTeleport>\n```\n\nWith `defer`, Vue doesn't give up on the first failed lookup. It waits until the current render cycle finishes — after every sibling in this same mount pass has had a chance to render — and tries the selector again. In the `AppShell` example, by the time that second lookup runs, `#modal-root` exists, because `AppShell`'s full template has finished mounting. **Key concept:** `defer` doesn't make Teleport reactive to the target appearing at *any* future point — it buys exactly one extra chance, timed to land after the surrounding tree's initial mount.\n\n**Stage 3 — why that one extra chance needed its own bug-fix history.** `defer` sounds like it should have been a clean, one-PR feature: delay a lookup, try again. In practice, \"after the current render cycle\" interacts with nearly every other thing Vue schedules around mounting, and getting it right took far more than one pass. Reading straight through the `vuejs\u002Fcore` changelog for `Teleport`-related fixes since `defer` shipped:\n\n| Version | Fixes |\n| --- | --- |\n| 3.5.12 | A disabled Teleport combined with `updateCssVars` ([#12113](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F12113)) |\n| 3.5.13 | Deferred Teleport updates arriving *before* `mounted` ([#12168](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F12168)) |\n| 3.5.15 | Deferred Teleport updates arriving both before **and** after mount ([#13350](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F13350)) |\n| 3.5.21 | Hydrating a disabled Teleport with an undefined target ([#11235](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F11235)) |\n| 3.5.22 | Multiple Teleports mounting correctly inside a custom element with `shadowRoot: false` ([#13900](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F13900)) |\n| 3.5.28 | Handling of the disabled Teleport's target anchor ([#14417](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F14417)) |\n| 3.5.31 | Deferring Teleport mount\u002Fupdate until `Suspense` resolves ([#8619](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F8619)) |\n| 3.5.32 | Teleport updates that arrive before its own deferred mount completes ([#14642](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F14642)) |\n| 3.5.33 | Stop moving a Teleport's children when the Teleport itself isn't mounted ([#14702](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F14702)) |\n| 3.5.39 | Nested async Teleport content resolving correctly under SSR ([#9431](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F9431)) |\n| 3.5.44 | Child-node ordering around `targetStart`\u002F`targetAnchor` ([#15818](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F15818)) |\n\nEleven patches, across more than a year of 3.5.x point releases, and the most recent one landed in the version this article verifies against. **Key concept:** this isn't a sign that `defer` is fragile or that you should avoid it — it's a sign of how many real, independent timing paths a single \"try again later\" feature has to account for: `Suspense` boundaries that resolve asynchronously, SSR hydration reconciling server-rendered markup against a client mount, disabled Teleports that have no target to resolve at all, and nested component trees unmounting mid-flight. Each row above is a real, reproducible scenario someone hit in production and filed. The practical upshot: `defer` genuinely fixes the ordering problem from Stage 1, and by 3.5.44 the surrounding edge cases around it are, as far as the project's own issue tracker shows, settled — but it took iterating against real usage, not a single clean implementation, to get there.\n\n## Edge Cases and Gotchas\n\n- **`defer` only works within the same mount\u002Fupdate cycle.** It is not a poll, a `MutationObserver`, or a retry-forever mechanism. If the target element is added a tick later — inside a `setTimeout`, after an unrelated async operation, or from code outside Vue entirely — `defer` will not find it, and you get the exact same \"Invalid Teleport target\" warning as without `defer`. The fix is for **ordering within one render**, not for a target that doesn't exist yet at all.\n- **`defer` plus `Suspense` needed a dedicated fix.** If your Teleport sits inside a component wrapped in `\u003CSuspense>`, the mount\u002Fupdate itself has to wait for the async dependency to resolve before `defer`'s \"try again\" logic even makes sense — that's specifically what 3.5.31 (PR [#8619](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F8619)) addressed. If you're on an older 3.5.x patch and see a deferred Teleport inside `Suspense` behave inconsistently, upgrading is the first thing to try before debugging your own code.\n- **A `:disabled` Teleport still needs a valid target expression, even if it never activates.** Several of the patches above (3.5.12, 3.5.21, 3.5.28) are specifically about the disabled state interacting badly with target resolution or hydration. Don't assume `disabled` sidesteps target-lookup bugs entirely; test the disabled path too if your Teleport toggles `disabled` dynamically.\n- **Multiple Teleports targeting the same element render in mount order, not template order.** If two different components each teleport into `#modal-root`, whichever one mounts first lands first in the DOM. This isn't something `defer` changes — it's an orthogonal fact worth knowing if you ever stack more than one Teleport onto a shared container and the visual order looks unexpected.\n- **SSR has its own resolution path, not the same as client-side mount.** The 3.5.39 fix (PR [#9431](https:\u002F\u002Fgithub.com\u002Fvuejs\u002Fcore\u002Fpull\u002F9431)) was specifically about nested async Teleport content during server rendering — a separate code path from the browser's synchronous `querySelector` lookup described in the mental model above. If you're teleporting content from a component that's itself inside an async `\u003CSuspense>` boundary during SSR, verify on 3.5.39 or later.\n\n## Best Practices\n\n- **Default to `defer` whenever the target might be a sibling rendered later in the same tree**, or whenever you're not in full control of render order (layout slots, portals provided by a parent, conditionally-mounted containers). It costs nothing when the target already exists early, and it's the documented fix for exactly this failure mode.\n- **Don't reach for `defer` as a substitute for guaranteeing the target exists.** If the container element is itself behind an async condition that might resolve *after* the current mount pass — a fetched layout, a lazily-loaded route — `defer` won't help. Either hoist the target markup above the async boundary, or hold off rendering the Teleport (`v-if`) until you know the target is present.\n- **Stay on a recent 3.5.x patch.** Given how many of the fixes above are genuinely separate scenarios rather than regressions of each other, an old 3.5.x pin (anything before .39, in particular) is leaving real, already-fixed Teleport bugs on the table for no reason.\n- **Test the `:disabled` toggle path explicitly**, not just the \"always enabled\" happy path, if your Teleport's `disabled` prop is ever reactive. That's where several of the subtler fixes above lived.\n\n## FAQ\n\n### Does `defer` fix every \"Teleport target not found\" warning?\n\nNo. It fixes the specific case where the target exists somewhere in the tree being mounted in the same render pass, just later than the Teleport itself. If the target is added by unrelated code after that pass completes — a later `setTimeout`, a separate async fetch that isn't part of the same mount — `defer` still fails the same way a non-deferred Teleport would.\n\n### What Vue version added `defer`?\n\nVue 3.5. It's documented in the Teleport guide and the built-in components API reference as a 3.5+ feature.\n\n### Why did a feature this small need a dozen follow-up patches?\n\nBecause \"retry the target lookup once, later\" touches nearly every other thing Vue schedules around mounting: `Suspense` resolution, SSR hydration, the disabled state, nested unmounts, and multi-Teleport ordering inside shadow DOM. Each patch in the table above fixed one specific, independently-reported interaction — not the same bug reappearing.\n\n### Is Vue 3.6 going to change how Teleport resolves its target?\n\nNothing in the public 3.6 release-candidate notes changes Teleport's resolution model as of this writing (3.6.0-rc.14). This article covers 3.5.x behavior, which is what every published 3.6 pre-release so far still runs for this feature.\n\n### Should I just wrap every Teleport in `defer` by default?\n\nIt's a reasonable default for any Teleport whose target isn't guaranteed to exist before the Teleport itself mounts, and the cost of adding it when it isn't strictly needed is effectively zero. It is not, however, a reason to stop thinking about where the target actually lives.\n\n### Does `defer` interact with batching the way `watch` or `computed` do?\n\nNo — `defer`'s one-time retry is a mount\u002Frender-cycle concern specific to Teleport's target resolution, not the shared reactivity notify buffer that `ref`, `computed`, and `watch` all queue through. If that separate mechanism is unfamiliar, [Vue's reactivity batch and flush() loop](https:\u002F\u002Fdev.to\u002Fparsajiravand\u002Fvue-36-when-a-watcher-throws-your-other-watchers-stall-25ge) covers it — useful context, not required here.\n\n## Cheat Sheet\n\n| Situation | What to do |\n| --- | --- |\n| Target is a static element always present before the Teleport mounts | No `defer` needed |\n| Target is a sibling that renders **later** in the same mount pass | Add `defer` |\n| Target may not exist until an unrelated async event later | `defer` won't help — gate the Teleport with `v-if` instead |\n| Teleport is inside `\u003CSuspense>` | Needs 3.5.31+ for correct `defer` interaction |\n| Teleport ever toggles `:disabled` | Verify on 3.5.28+ for correct anchor handling |\n| Teleporting during SSR with nested async content | Needs 3.5.39+ |\n| Multiple Teleports share one target | They render in **mount order**, not template order |\n\n```vue\n\u003C!-- The safe default when you're not 100% sure the target exists yet -->\n\u003CTeleport defer to=\"#modal-root\">\n  \u003Cdiv v-if=\"open\" class=\"modal\">…\u003C\u002Fdiv>\n\u003C\u002FTeleport>\n```\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-teleport-defer-target-timing\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\n## Key Takeaways\n\n- `\u003CTeleport>` resolves its target selector exactly once, synchronously, at mount time — it does not watch for the target to appear later.\n- The classic failure is a target that's a sibling rendered **after** the Teleport in the same component tree; `defer` (Vue 3.5+) fixes this by retrying the lookup once, after the current render cycle, similar in timing to the `mounted` hook.\n- `defer` is not a general \"wait until the target exists\" mechanism — it only covers the same mount\u002Fupdate cycle, not a target added later by unrelated code.\n- Getting `defer` right across Suspense, SSR, the disabled state, and multi-Teleport ordering took more than a dozen separate patches through 3.5.x, the most recent in 3.5.44 — treat an old 3.5 pin as leaving real fixes unclaimed.\n- Multiple Teleports sharing one target land in mount order, not template order — an orthogonal fact worth knowing even when `defer` isn't in play.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 8-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-teleport-defer-target-timing\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\nA target that exists in your app and a target that exists in the DOM *at the exact moment Vue looks for it* are two different claims — `defer` closes the gap between them for one common case, not for all of them. The next time a Teleport \"just doesn't show up,\" check mount order before you check your own code.\n\nHave you hit the \"Invalid Teleport target\" warning in a layout you were sure was correct? Drop what finally fixed it in the comments.\n\n\u003C!-- related:start -->\n\n## 📚 Read next\n\n- [Vue Composables: The Shared State Trap (+ Cheat Sheet)](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-composables-shared-state-trap)\n- [Vue defineModel: The v-model Contract, Explained](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-definemodel-v-model-contract)\n- [Vue watch vs watchEffect: The Complete Guide](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-watch-vs-watcheffect)\n\n\u003C!-- related:end -->\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":109,"canonical":464,"description":110},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Fvue-weekly-teleport-defer-target-timing","01a16761-e188-7104-b670-52be2b07b7bc",{"name":467,"part":468,"total":468,"items":469},"Vue Deep Dive",10,[470,474,479,483,487,491,495,499,503,507],{"slug":471,"title":472,"publishedAt":473,"readingMinutes":112},"vue-weekly-reactivity-ref-vs-reactive","Vue Reactivity Explained: ref vs reactive (+ Cheat Sheet)","2026-08-24T13:57:38.080Z",{"slug":475,"title":476,"publishedAt":477,"readingMinutes":478},"vue-weekly-nexttick-batching-dom-updates","Vue nextTick Explained: How DOM Updates Are Batched","2026-09-14T12:19:00.606Z",13,{"slug":480,"title":481,"publishedAt":482,"readingMinutes":478},"vue-weekly-composables-shared-state-trap","Vue Composables: The Shared State Trap (+ Cheat Sheet)","2026-09-22T19:37:11.460Z",{"slug":484,"title":485,"publishedAt":486,"readingMinutes":106},"vue-weekly-computed-caching-invalidation","Vue computed(): What It Caches and When It Reruns","2026-09-30T12:10:53.362Z",{"slug":488,"title":489,"publishedAt":490,"readingMinutes":478},"vue-weekly-definemodel-v-model-contract","Vue defineModel: The v-model Contract, Explained","2026-10-05T06:11:02.623Z",{"slug":492,"title":493,"publishedAt":494,"readingMinutes":102},"vue-weekly-watch-vs-watcheffect","Vue watch vs watchEffect: The Complete Guide","2026-10-12T06:10:54.504Z",{"slug":496,"title":497,"publishedAt":498,"readingMinutes":102},"vue-weekly-reactivity-batch-watcher-throws","Vue 3.6: When a Watcher Throws, Your Other Watchers Stall","2026-10-19T06:10:59.563Z",{"slug":500,"title":501,"publishedAt":502,"readingMinutes":478},"vue-weekly-vfor-directive-unmounted-hook","Vue v-for: The Directive Unmounted Hook Bug, Fixed in 3.5.41","2026-10-26T05:39:58.065Z",{"slug":504,"title":505,"publishedAt":506,"readingMinutes":106},"vue-weekly-provide-inject-mutation-trap","Vue provide\u002Finject: Why You Should Never Skip readonly()","2026-11-02T05:32:04.674Z",{"slug":46,"title":109,"publishedAt":113,"readingMinutes":112},{"id":509,"locked":18},"01a16761-e249-720c-b0f3-fd06004fbeef",[511],{"id":45,"slug":46,"title":48,"_count":512},{"questions":51},[514],{"locale":13,"slug":46},{"id":45,"slug":46,"title":48,"_count":516,"questionCount":51},{"questions":51},[]]