skip to content

In Vue 3, what happens to the existing HTML inside the mount container when app.mount() runs?

level: middleimportance: should knowfreq 35%

answer

  1. container stays, content goes
  2. unlike Vue 2
  3. template or render wins
  4. otherwise innerHTML is the template
  5. v-cloak removed, data-v-app added

basics

~20 s

The container element stays and its content is replaced by the rendered app. If the root component has no template or render function, Vue first uses the container's innerHTML as the root template, which needs the full build.

solid answer

~40 s

In Vue 3 the container is not part of the app: `mount()` keeps the element and replaces only what is inside it. If the root component has a `template` or a render function, which every SFC has, the existing content is cleared and the rendered output takes its place. If the root has neither, Vue treats the container's `innerHTML` as the root component's template, an **in-DOM root template**, which requires the build that includes the runtime template compiler. After mounting, Vue removes a `v-cloak` attribute from the container and adds `data-v-app`. This differs from Vue 2, where the mount element itself was replaced. In-DOM templates execute expressions, so the server must never place untrusted content inside that container.

code

ts · 9 lines
ts
import { createApp, ref } from 'vue' // full build needed for in-DOM templates

// no template or render: #app's innerHTML becomes the template
createApp({
  setup() {
    const count = ref(0)
    return { count }
  },
}).mount('#app')

go deeper

for a junior

Recall that the container stays, its content is replaced, and a root without a template takes the container's HTML as its template.

for a middle

Explain the template-or-render check, v-cloak and data-v-app, the full-build requirement and the Vue 2 difference.

for a senior

Spot the template-injection risk of in-DOM templates on server-rendered pages and keep untrusted content out of them.

for a principal

Decide whether a legacy page should be enhanced with in-DOM templates or migrated to precompiled components, weighing risk and bundle size.

## The container is a host, not part of the app `app.mount(container)` takes an element, or a selector for one, and renders the root component **inside** it. The element itself, with its id, classes and attributes, remains in the page. The Vue guide states it plainly: the container element itself is not considered part of the app. ## Step by step 1. Vue resolves the container element. 2. It checks the root component. If the root has **neither** a `template` option **nor** a render function, Vue copies the container's `innerHTML` into the root component's `template`. This is the **in-DOM root template** pattern. 3. It clears the container's existing content. 4. It compiles the template if needed and renders the root component into the now-empty container. 5. It removes the `v-cloak` attribute from the container, if present, and sets a `data-v-app` attribute on it. So in both cases the original markup disappears; the difference is whether it was used as the template first. | Root component | What the old container content becomes | |---|---| | SFC or render function | discarded, replaced by the rendered output | | options object with `template` | discarded, replaced by the rendered output | | options object with neither | read as the template, then replaced by its rendered result | | created with `createSSRApp` | kept and hydrated rather than replaced | ## In-DOM root templates This pattern lets Vue enhance HTML that a server already produced, without a build step: ```html <div id="app" v-cloak> <button @click="count++">{{ count }}</button> </div> ``` The root component then only needs state, for example a `setup()` returning `count`. Compiling that HTML happens in the browser, so the page must load Vue's build that includes the runtime template compiler; the runtime-only build used with SFCs cannot do it. The `v-cloak` attribute plus a CSS rule such as `[v-cloak] { display: none }` hides the raw mustaches until mount removes the attribute. ## Avoiding a flash of raw template syntax With an in-DOM root template, the browser displays the container's HTML as soon as it parses it, before the Vue bundle has loaded. For that moment users see raw `{{ count }}` text and buttons that do nothing. The standard fix has two parts: 1. add `v-cloak` to the container in the server template; 2. add a global CSS rule, `[v-cloak] { display: none; }`. Mount removes the attribute from the container after rendering, so the rule stops matching and the rendered content appears. The trade-off is a blank area until JavaScript runs, so reserve its space with CSS to avoid layout shift, and consider whether real server-rendered placeholder content would serve users better, which means a precompiled component and a different mounting strategy. ## The security consequence Because the container's HTML becomes a **template**, every `{{ }}` and directive inside it is evaluated as a JavaScript expression. If the server writes user-supplied text into that container, a user can inject template expressions that run in other users' browsers. Vue's source marks this path as unsafe for exactly that reason. Defences: - keep user-generated content out of any element that serves as an in-DOM template; - or render such content from data, through interpolation or `v-text`, which escapes it, instead of letting the server paste it into the template; - or skip the pattern entirely and ship precompiled components. ## Vue 2 versus Vue 3 In Vue 2, mounting a component that had a template **replaced the mount element** with the rendered root. In Vue 3 the rendered app is placed inside the element, replacing its `innerHTML`. Code or CSS that targeted `#app` expecting it to vanish, or expecting the root's own element to carry the id, needs adjusting when migrating. ## Common mistakes - Expecting server-rendered placeholder content inside the container to remain after a client-only mount. - Forgetting `v-cloak` styling and showing raw template syntax briefly on slow pages. - Using an in-DOM root template with the runtime-only build and getting an empty mount. - Printing unescaped user content into a container that doubles as a template.

  • Why does v-cloak disappear from the container after mount?
    The DOM-specific `mount` removes the `v-cloak` attribute from the container once the app has rendered. Paired with a CSS rule that hides `[v-cloak]` elements, this keeps uncompiled mustache syntax invisible until Vue has replaced it with rendered output.
  • What is the data-v-app attribute that appears on the container?
    After mounting, Vue sets an empty `data-v-app` attribute on the container. The runtime never reads it back; it is simply a visible marker that the element hosts a mounted app, handy in CSS selectors or when inspecting the page. It carries no configuration.

saying these in an interview costs you the question

  • Vue 3 replaces the container element itself with the root component
  • Existing content in the container is kept and the app is appended after it
  • An in-DOM root template works with the runtime-only build
  • Server-rendered user content inside an in-DOM template is safe because Vue escapes it
  • Vue uses the container's HTML as the template even when the root has a render function