vuejs/core · error · Error

Invalid async component load result: ${comp}

Error message

Invalid async component load result: ${comp}

What it means

Thrown in __DEV__ by defineAsyncComponent's loader when the resolved value is truthy but is neither a plain object nor a function. After interop unwrapping (esModule default / Module toStringTag), a valid Vue component must be an options object or a render function. apiAsyncComponent.ts:112 aborts because the loader returned a non-component value (e.g. a string, number, array, or a Promise).

Source

Thrown at packages/runtime-core/src/apiAsyncComponent.ts:112

          .then((comp: any) => {
            if (thisRequest !== pendingRequest && pendingRequest) {
              return pendingRequest
            }
            if (__DEV__ && !comp) {
              warn(
                `Async component loader resolved to undefined. ` +
                  `If you are using retry(), make sure to return its return value.`,
              )
            }
            // interop module default
            if (
              comp &&
              (comp.__esModule || comp[Symbol.toStringTag] === 'Module')
            ) {
              comp = comp.default
            }
            if (__DEV__ && comp && !isObject(comp) && !isFunction(comp)) {
              throw new Error(`Invalid async component load result: ${comp}`)
            }
            resolvedComp = comp
            return comp
          }))
    )
  }

  return defineComponent({
    name: 'AsyncComponentWrapper',

    __asyncLoader: load,

    __asyncHydrate(el, instance, hydrate) {
      const wasConnected = el.isConnected
      let patched = false
      ;(instance.bu || (instance.bu = [])).push(() => (patched = true))
      const performHydrate = () => {
        // skip hydration if the component has been patched

View on GitHub (pinned to a2b40db9a8)

Solutions

  1. Verify the loader actually imports a Vue component: check that the module's default export is an options object or a function.
  2. If using named exports, return the correct binding, e.g. `() => import('./MyComp.vue').then(m => m.MyComp)`.
  3. Ensure retry()/onError handlers return their value so the resolved value stays a component (the related 'resolved to undefined' warn at line 99 covers that case).
  4. Add a runtime type check in the loader and map non-components to a real component or a thrown error.

Example fix

// before
const AsyncComp = defineAsyncComponent(() => import('./config.json'))

// after
const AsyncComp = defineAsyncComponent(() => import('./RealComponent.vue'))
Defensive patterns

Strategy: type-guard

Validate before calling

// Validate the loader's resolved value before registering the async component.
function isComponent(v: unknown): boolean {
  return (typeof v === 'object' && v !== null) || typeof v === 'function'
}
const loader = async () => {
  const mod = await import('./MyComp.vue')
  const comp = mod?.__esModule ? mod.default : mod
  if (!isComponent(comp)) {
    throw new Error(`async loader resolved to non-component: ${comp}`)
  }
  return comp
}
defineAsyncComponent(loader)

Type guard

function isVueComponent(v: unknown): v is object | ((...a: any[]) => any) {
  return (typeof v === 'object' && v !== null) || typeof v === 'function'
}

Prevention

When it happens

Trigger: An async loader whose import returns a non-component — e.g. `() => import('./constants')` where the module exports a string/number; a dynamic import of a .json or a non-Vue module; a loader that forgets `.default` after an interop edge case; returning a Promise-of-Promise that resolves to a raw value.

Common situations: Typo in the import path pointing at a non-component module; misconfigured dynamic import in a route config; a barrel re-export that swaps a component for a plain value; tree-shaking that leaves a module exporting a non-component default.

Related errors


AI-assisted analysis of vuejs/core@a2b40db9a8 (2026-08-12). Data as JSON: /api/errors/8c4c4afbf1107d24. Report an issue: GitHub.