In Vue 3, a page logs 'Failed to resolve component: UserBadge' and renders an empty unknown element; how do you work out which registration problem causes it?
answer
- where the tag was looked for
- local scope stops at the file
- one registry per app
- spelling of the registered key
- native custom elements
basics
~20 sVue logs Failed to resolve component when a template tag matches no import, local registration or app-level registration. Check that the using file imports it, that the global registration is on this app, that the registered spelling matches, and that it is not a custom element.
solid answer
~50 sThe warning means the template compiler emitted a runtime lookup for the tag and nothing matched: no `<script setup>` binding, no explicit self name, no local registration, no registration on *this* app. Walk the causes in order. First, **scope**: the tag is in a child, but only the parent imported the component; local registration is not inherited, so the child must import it. Second, **the app**: a global registration made on a different `createApp` instance (a second app, a test mount, a micro-frontend) is invisible here. Third, **spelling**: a component registered as `'user-badge'` resolves only `<user-badge>`, not `<UserBadge>`; register in PascalCase. Fourth, **it was never meant to be a Vue component**: a native custom element (web component) trips the same warning, and the fix is `compilerOptions.isCustomElement`, which the warning text itself suggests. Also check for a typo or an import whose local name differs from the tag.
code
vue · 12 lines<script setup lang="ts">
// ProfileHeader.vue: the parent imported UserBadge, this child did not
import UserBadge from './UserBadge.vue'
defineProps<{ userId: number }>()
</script>
<template>
<header>
<UserBadge :user-id="userId" />
</header>
</template>go deeper
Know that the warning means the tag matched no import or registration, and check the file that renders the tag first.
Explain the lookup order and why local scope, the per-app registry and the registered spelling each cause the same warning.
Diagnose the warning systematically from the component trace, including isolated test mounts and native custom elements, and remove the root cause rather than adding a global registration.
Decide how registration is organised across several apps or micro-frontends so a component resolves the same way in every mount.
## What the warning actually means When Vue compiles a template, each tag that is not a native element, a built-in component or a `<script setup>` binding is compiled into a **runtime lookup** by name. At render time the lookup searches, in order: 1. the component's own explicit `name` (for recursion); 2. its **local** registrations (the `components` option); 3. the **global** registry of the application it belongs to (`app.component()`); 4. for a tag matching its own filename, the component itself. If all of that fails, development builds log `Failed to resolve component: UserBadge` with a hint about `compilerOptions.isCustomElement`, and the tag is rendered as a plain element with that name. Production builds skip the warning, so the page simply shows nothing where the component should be. ## A checklist of causes | Cause | Symptom | Fix | |---|---|---| | Missing import in this file | Works in the parent, fails in the child | Import it where the tag is used | | Local registration expected to be inherited | Same as above | Local scope never reaches descendants | | Global registration on another app | Works in the main app, fails in a second app or an isolated mount | Register on every app that renders it | | Registered key in kebab-case, tag in PascalCase | `<user-badge>` works, `<UserBadge>` fails | Register under `UserBadge` | | Import alias differs from the tag | `import Badge from ...` but `<UserBadge>` | Use the import's local name as the tag | | Tag is a native custom element | Warning for a hyphenated web component | Declare it with `compilerOptions.isCustomElement` | ## Working through it A practical order of investigation, cheapest first: 1. **Read the warning trace.** Development warnings include the component trace, so you know which component's template contains the unresolved tag. The problem lives in that file, not where the component was first used. 2. **Look for the binding in that file.** In `<script setup>` the tag must match an import or a declared binding. A tag can differ in casing from the binding (`<user-badge>` for `UserBadge` is fine), but not in the word itself. 3. **If it is meant to be global**, confirm the registration runs on the same application instance that renders this tree. Separate `createApp` calls, a second app for a widget, or a component test that mounts in isolation each start with an empty registry. 4. **Check the registered spelling.** Vue tries the tag as written, camelized and capitalized, but never transforms the registered key, so a kebab-case key only answers to a kebab-case tag. 5. **Ask whether it is a Vue component at all.** A third-party web component such as `<sl-button>` is not in any registry. The hint in the warning exists for this case: tell the compiler which tags are custom elements so it stops treating them as components. ## Related warnings worth recognising - `Component "UserBadge" has already been registered in target app.` appears when the same name is registered twice on one app, often because a registration module runs twice; the later registration replaces the earlier one. - `Do not use built-in or reserved HTML elements as component id: button` appears when a component is registered under a native tag or a built-in name; such a name cannot resolve as a component in templates the way you intend. ## Preventing it - Prefer local imports: a missing import is then an error your editor and type checker catch before runtime. - Keep global registrations in one module, in PascalCase, applied to each app the product creates. - Treat the warning as an error in development tooling so it is not lost in a noisy console.
- Why does the same missing component produce no warning in a Vue 3 production build?Warnings are development-only diagnostics stripped from production builds. The lookup still fails and the tag is still rendered as a plain unknown element, so the component silently disappears. That is why the warning should be fixed, or promoted to a test failure, during development.
- A Vue 3 component test mounts `ProfileHeader` alone and gets `Failed to resolve component: UserBadge`, although the real app renders it fine. What is going on?The real app registers `UserBadge` globally, but the test creates its own isolated application with an empty registry. Either make `ProfileHeader` import `UserBadge` locally, which fixes both, or register it on the test's app as well.
saying these in an interview costs you the question
- Registering a component in a parent makes it resolvable in the child that renders it.
- Failed to resolve component always means the component file does not exist.
- A kebab-case registration also resolves the PascalCase tag.
- Global registrations are shared by every Vue app on the page.
- Custom elements need to be registered with app.component() to stop the warning.