{"record":{"id":"651c84cf9a6d5f63","repo":"vuejs/vue","slug":"invalid-async-component-load-result-comp","errorCode":null,"errorMessage":"Invalid async component load result: ${comp}","messagePattern":"Invalid async component load result: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/v3/apiAsyncComponent.ts","lineNumber":99,"sourceCode":"          .then((comp: any) => {\n            if (thisRequest !== pendingRequest && pendingRequest) {\n              return pendingRequest\n            }\n            if (__DEV__ && !comp) {\n              warn(\n                `Async component loader resolved to undefined. ` +\n                  `If you are using retry(), make sure to return its return value.`\n              )\n            }\n            // interop module default\n            if (\n              comp &&\n              (comp.__esModule || comp[Symbol.toStringTag] === 'Module')\n            ) {\n              comp = comp.default\n            }\n            if (__DEV__ && comp && !isObject(comp) && !isFunction(comp)) {\n              throw new Error(`Invalid async component load result: ${comp}`)\n            }\n            return comp\n          }))\n    )\n  }\n\n  return () => {\n    const component = load()\n\n    return {\n      component,\n      delay,\n      timeout,\n      error: errorComponent,\n      loading: loadingComponent\n    }\n  }\n}","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/vuejs/vue/blob/9e88707940088cb1f4cd7dd210c9168a50dc347c/src/v3/apiAsyncComponent.ts#L81-L117","documentation":"Thrown by Vue's defineAsyncComponent (src/v3/apiAsyncComponent.ts) only in __DEV__ builds. After the loader resolves and ES-module interop unwrapping runs (comp = comp.default when the module is flagged __esModule or tagged 'Module'), the result must be a Vue component — i.e. an object (options/defineComponent) or a function (functional component). If the resolved value is a non-null primitive (string, number, boolean, symbol, bigint), the loader is returning something that can never render, so the library throws rather than render a silent blank. In production builds the check is elided and the bad value is returned as-is.","triggerScenarios":"Loader's dynamic import points at a non-component module (a constant, a JSON file, a plain string export). A loader that does `() => import('./constants').then(m => m.SOME_STRING)` returning a named string export. A loader wrapped in retry()/onError plumbing that drops the resolved value and resolves to a primitive. A module whose default export is a primitive rather than a component definition. A hand-written loader returning `() => 'MyComponent'` instead of an import.","commonSituations":"Refactoring a component directory and leaving an import path pointing at an index that now re-exports a constant. Migrating a CommonJS module whose module.exports was set to a string/number. Webpack chunk misconfiguration returning the module ID instead of its namespace. A Babel/swc interop edge case where __esModule is absent so the namespace object itself (a primitive field) is returned. Forgetting `default` when the component is exported as default and interop did not flag __esModule.","solutions":["Open the target module and confirm its default export is a Vue component object or function; fix the export.","If the component is a named export, map it in the loader: `defineAsyncComponent(() => import('./C.vue').then(m => m.NamedComp))`.","If interop is not flagging the namespace, unwrap default explicitly: `defineAsyncComponent(() => import('./C').then(m => m.default || m))`.","Verify the import path resolves to a .vue file or component module (check for accidental import of a constants/util file).","Reproduce in a dev build to get the throw, then inspect `comp` in a debugger at apiAsyncComponent.ts:99 to see exactly what the loader returned."],"exampleFix":"// before — loader resolves to a primitive string\nconst C = defineAsyncComponent(() => import('./labels').then(m => m.TITLE))\n\n// after — resolve to an actual component\nconst C = defineAsyncComponent(() => import('./TitleComponent.vue'))\n// or, named component export\nconst C = defineAsyncComponent(() => import('./mods').then(m => m.TitleComponent))","handlingStrategy":"type-guard","validationCode":"// Wrap any loader passed to defineAsyncComponent so a non-component\n// resolution becomes a rejected promise (handled by onError / errorComponent)\n// instead of throwing inside the lib.\nimport { defineAsyncComponent } from 'vue'\n\nconst asComponent = (m) => {\n  const comp =\n    m && (m.__esModule || m[Symbol.toStringTag] === 'Module') ? m.default : m\n  if (comp == null) throw new Error('Async loader resolved to undefined')\n  if (typeof comp !== 'object' && typeof comp !== 'function') {\n    throw new Error(`Async loader resolved to non-component: ${typeof comp}`)\n  }\n  return comp\n}\n\nconst AsyncComp = defineAsyncComponent({\n  loader: () => import('./MaybeComponent.vue').then(asComponent),\n  errorComponent: FallbackComp\n})","typeGuard":"// Narrow an unknown async resolution to a Vue-component-like value.\nconst isVueComponentLike = (c) =>\n  c != null && (typeof c === 'object' || typeof c === 'function')\n\n// Use inside a custom loader before returning:\n//   const m = await import(path)\n//   const comp = m.default ?? m\n//   if (!isVueComponentLike(comp)) {\n//     return Promise.reject(new Error(`${path} did not export a component`))\n//   }\n//   return comp","tryCatchPattern":"// defineAsyncComponent already swallows loader errors via onError/errorComponent.\n// Provide both so a non-component resolution surfaces in the UI instead of throwing:\ndefineAsyncComponent({\n  loader: () => import('./Risky.vue').then(m => m.default ?? m),\n  loadingComponent: LoadingSpinner,\n  errorComponent: ErrorBox,\n  timeout: 8000,\n  onError(error, retry, fail, attempts) {\n    if (attempts <= 2) retry()\n    else fail()\n  }\n})","preventionTips":["Always point async loaders at .vue files or modules explicitly exported as components; avoid importing barrel/index files whose contents you do not control.","Prefer `() => import('./X.vue')` over `() => import('./X')` so the bundler resolves the SFC unambiguously.","Map named exports explicitly: `.then(m => m.Named)` rather than relying on default interop.","Provide an `errorComponent` so any loader failure (including this throw in dev) is visible in the UI instead of crashing render.","Remember this throw is dev-only — a production build will silently render nothing, so catch bad resolutions during dev/CI with a loader wrapper."],"tags":["vue","async-component","defineasynccomponent","dynamic-import","dev-only"],"analyzedSha":"9e88707940088cb1f4cd7dd210c9168a50dc347c","analyzedAt":"2026-08-11T22:22:01.295Z","schemaVersion":2},"datasetVersion":"2026-08-12T04:17:13.124Z"}