In Vue 3, what is the difference between registering a component with app.component() and importing it locally in `<script setup>`, and which should you prefer?
answer
- who can see the component
- app-wide registry vs one file
- what the bundler can drop
- explicit dependency graph
basics
~20 sVue's app.component() registers a component app-wide, so any template can use it without importing, but it always ships in the bundle. A local import in script setup is visible only in that file and tree-shakes cleanly, so it is the default.
solid answer
~50 s`app.component('BaseButton', BaseButton)` puts the component in the application's registry; every template in that app, including the registered components themselves, can then use `<BaseButton>` without an import. In `<script setup>`, `import BaseButton from './BaseButton.vue'` is enough to use it in that file's template: no registration step, and it is visible only there. The Vue docs list two drawbacks of global registration: the build cannot **tree-shake** a globally registered component, so it ships even if no page uses it, and dependencies become implicit, so you cannot see from a parent where a child comes from. Local imports keep the dependency graph explicit, work with editor navigation and type checking out of the box, and let the bundler put a component only in the chunks that use it. Reserve global registration for a handful of truly ubiquitous primitives.
go deeper
Know both forms, that script setup needs only an import, and that a global registration makes a tag usable in every template of the app.
Explain why a registered component cannot be tree-shaken and why local registration does not reach descendant components.
Audit an app's global registrations, move rarely used ones to local imports and measure what leaves the entry chunk.
Define the policy for which base components, if any, a design system registers globally, trading call-site convenience against bundle cost and discoverability.
## Two ways Vue finds a component When a Vue template contains a tag such as `<BaseButton>`, Vue must know which component implementation that tag means. Vue 3 offers two ways to make that link, called **registration**: - **Global registration** with `app.component(name, component)` adds the component to the registry of one application instance created by `createApp`. - **Local registration** makes a component available to a single component. In `<script setup>` it is just an `import`; in a non-`<script setup>` component it is the `components` option. ```ts import { createApp } from 'vue' import App from './App.vue' import BaseButton from './components/BaseButton.vue' createApp(App).component('BaseButton', BaseButton).mount('#app') ``` `app.component()` returns the app, so calls can be chained, and calling it with only a name returns the component already registered under that name. ## How they compare | Aspect | `app.component()` | Local import in `<script setup>` | |---|---|---| | Visible in | Every template of that app | Only the importing file | | Needs an import at the use site | No | Yes | | Tree-shaking | Not possible; it always ships | Unused components are dropped | | Chunk placement | Wherever the registering module loads, usually the entry | Only the chunks that import it | | Finding the implementation | Search the registry setup | Follow the import | | Template type checking | Needs a `GlobalComponents` type declaration | Works from the import | ## Why global registration defeats tree-shaking A bundler removes a module only when nothing references it. `app.component('BaseButton', BaseButton)` is itself a reference, made in the app's startup code, and the bundler cannot know which string tags templates will ask the registry for at runtime. So every globally registered component: 1. is included in the build even if no template uses it; 2. is typically pulled into the **entry chunk**, because the registration runs at startup, even when only one lazily loaded page renders it; 3. hides the real dependency from tools that follow imports. A local import has none of these costs: if a page that imports `DataGrid` is split into its own chunk, `DataGrid` travels with that chunk. ## The explicitness argument The docs compare heavy global registration to **too many global variables**. In a large codebase, a template that says `<UserBadge>` with no import gives the reader no path to the file, and renaming or deleting the component cannot be checked by following imports. A local import makes the dependency graph visible to people, editors and build tools alike. One scoping rule surprises people: **a locally registered component is not available in its descendants**. If a parent imports `UserBadge`, a child that renders `<UserBadge>` must import it too. Global registration is the only mechanism that makes a component visible everywhere. ## When global registration is still reasonable - A very small set of **base components** used on nearly every screen, where an import in every file adds noise. - Components contributed by a library whose documented setup registers them app-wide. - Prototypes and in-DOM templates without a build step, where there is no import to write. For everything else, prefer local imports: the default Vue project setup and `<script setup>` make them the path of least resistance.
- A parent in Vue 3 imports `UserBadge` in `<script setup>`; a child component renders `<UserBadge>` without importing it. What happens?The child's template cannot resolve the tag: local registration is scoped to the component that did it and is not inherited by descendants. Vue logs a `Failed to resolve component` warning in development and renders an unknown element. The child must import `UserBadge` itself, or the component must be registered globally.
- If an app has two `createApp` instances, is a component registered with `app.component()` on one available in the other?No. Each application instance has its own registry, created by its own `createApp` call, so registration on one app does not leak into another. That per-app scoping is a deliberate change from Vue 2's single global constructor.
saying these in an interview costs you the question
- Globally registered components that no page uses are removed by tree-shaking.
- A component imported by a parent is automatically available in all of its children.
- In script setup you must also list imported components in a components option.
- app.component registers the component for every Vue app on the page.