vuejs/vue-router · info

Failed to resolve async component ${key}: ${reason}

Error message

Failed to resolve async component ${key}: ${reason}

What it means

This is a dev-only warn (console.warn) shown when <router-link> is used with a scoped slot (v-slot) but without the custom prop. In Vue Router 4 the default anchor rendering will change for slot content, so the library warns you to opt in with custom to take full control of the rendered element. It fires once per session (warnedCustomSlot flag).

Source

Thrown at src/util/resolve-components.js:40

        const resolve = once(resolvedDef => {
          if (isESModule(resolvedDef)) {
            resolvedDef = resolvedDef.default
          }
          // save resolved on async factory in case it's used elsewhere
          def.resolved = typeof resolvedDef === 'function'
            ? resolvedDef
            : _Vue.extend(resolvedDef)
          match.components[key] = resolvedDef
          pending--
          if (pending <= 0) {
            next()
          }
        })

        const reject = once(reason => {
          const msg = `Failed to resolve async component ${key}: ${reason}`
          process.env.NODE_ENV !== 'production' && warn(false, msg)
          if (!error) {
            error = isError(reason)
              ? reason
              : new Error(msg)
            next(error)
          }
        })

        let res
        try {
          res = def(resolve, reject)
        } catch (e) {
          reject(e)
        }
        if (res) {
          if (typeof res.then === 'function') {
            res.then(resolve, reject)
          } else {

View on GitHub (pinned to 680ccc68c5)

Solutions

  1. Add the custom prop to <router-link> when using v-slot and render your own <a>.
  2. If you want the default <a> rendering, drop the scoped slot and use the default slot instead.
  3. Suppress by acknowledging the future behavior change if it is intentional (warning is dev-only, once).

Example fix

// before
<router-link v-slot="{ href, navigate }"><a :href="href" @click="navigate">Go</a></router-link>
// after
<router-link :to="to" custom v-slot="{ href, navigate }"><a :href="href" @click="navigate">Go</a></router-link>
Defensive patterns

Strategy: retry

Validate before calling

// preflight: ensure the chunk URL is reachable before navigating
async function chunkReachable (importFn) {
  try { await importFn(); return true } catch { return false }
}

Type guard

function isChunkError (err) {
  return err instanceof Error && /Loading chunk|Loading CSS chunk|ChunkLoadError/i.test(err.message)
}

Try / catch

function lazy (importFn, retries = 2) {
  return () => importFn().catch(err => {
    if (retries-- > 0 && isChunkError(err)) {
      window.location.reload()
    }
    throw err
  })
}
{ path: '/admin', component: lazy(() => import('@/views/Admin')) }

Prevention

When it happens

Trigger: Rendering <router-link v-slot="{ href, navigate }">...</router-link> in dev mode without the custom prop, while also using the default rendering path (i.e. relying on router-link to render the <a> itself).

Common situations: Migrating from vue-router 3 to 4 or using next releases; copying v-slot examples that assume custom; fully custom link components (e.g. wrapping <nuxt-link>-style markup) that forget custom.

Related errors


AI-assisted analysis of vuejs/vue-router@680ccc68c5 (2026-09-02). Data as JSON: /api/errors/ff8779a77acd3773. Report an issue: GitHub.