できない.dev

Vue で「Failed to resolve component」が解決できない

[Vue warn]: Failed to resolve component は、テンプレートで使ったコンポーネントが登録されていないときに出る。<script setup> では import するだけでローカル登録され、Options API では components への登録、全体で使うならグローバル登録が必要。

公開: 更新:

実行例あり(2026-09-18 に実環境で検証)

要約

[Vue warn]: Failed to resolve component: MyButton は、テンプレートに書いたコンポーネントを Vue が見つけられないときの警告です。
原因はほぼ「登録漏れ」か「名前のずれ」。<script setup> を使っていれば import するだけで自動的にローカル登録されるので、まずはそこを確認します。

実行例

登録し忘れたコンポーネントは警告が出たあと空ノードとして描画され、出力は <div><!----></div> で止まる。components オプションによる局所登録と app.component() による全体登録は、どちらも同じ <button>OK</button> を出力する。

$ node bad.mjs
[Vue warn]: Failed to resolve component: MyButton
If this is a native custom element, make sure to exclude it from component resolution via compilerOptions.isCustomElement. 
  at <App>
[Vue warn]: Component <Anonymous> is missing template or render function. 
  at <Anonymous> 
  at <App>
出力: <div><!----></div>
$ node solve_local.mjs
出力: <div><button>OK</button></div>
$ node solve_global.mjs
出力: <div><button>OK</button></div>
終了コード: 0(button が出力されていれば解消)

— 2026-09-18 時点の出力

検証環境

検証日
実行環境
node:20Debian GNU/Linux 12 (bookworm)
バージョン
  • Node.js 20.20.2
  • npm 10.8.2
  • Python 3.11.2
  • Git 2.39.5

この記事の「実行例」は、上記の環境で実際にコマンドを実行して得られた出力をそのまま掲載しています。 再現手順はリポジトリの検証スクリプトとして管理し、定期的に再実行して出力を更新しています。

よくある原因

  1. components への登録漏れ: <script setup> を使わない Options API では、import しただけでは使えず components への登録が必要。
  2. 名前のずれ: <MyButton /> と書いたのに登録名が Mybutton になっている、などのタイポ。
    Vue は PascalCase と kebab-case を相互に解決しますが、綴り自体が違うと解決できません。
  3. グローバル登録の順序: app.component() をコンポーネント使用後やマウント後に呼んでいる。
  4. 動的コンポーネント: <component :is="name"> の name に未登録の文字列を渡している。

解決策

1. <script setup> なら import だけでよい

<script setup>
import MyButton from './MyButton.vue'
</script>
 
<template>
  <MyButton />
</template>

コンポーネントの登録(新しいタブで開く) のとおり、<script setup> 内で import したコンポーネントは自動でローカル登録されます。

2. Options API は components に登録する

<script>
import MyButton from './MyButton.vue'
 
export default {
  components: { MyButton },
}
</script>

3. 全体で使うならグローバル登録

import { createApp } from 'vue'
import App from './App.vue'
import MyButton from './components/MyButton.vue'
 
const app = createApp(App)
app.component('MyButton', MyButton) // mount より前に登録
app.mount('#app')

グローバル登録は便利な一方、未使用でもビルドに含まれ tree-shaking されにくいので、使う箇所が限られるならローカル登録を優先します。

4. 動的コンポーネントは定義を渡す

<script setup>
import Foo from './Foo.vue'
import { shallowRef } from 'vue'
 
const current = shallowRef(Foo) // 文字列 'Foo' ではなく定義を渡す
</script>
 
<template>
  <component :is="current" />
</template>

文字列名で切り替えたい場合は、名前からコンポーネントへのマップを自前で用意するか、対象をグローバル登録しておきます。

この記事は役立ちましたか?