swc-project/swc · error

`{module_specifier}` matched `{prefix}` (from tsconfig.paths

Error message

`{module_specifier}` matched `{prefix}` (from tsconfig.paths) but failed to resolve:
{errors:?}

What it means

TsConfigResolver (tsc.rs) applies tsconfig `paths` patterns to a module specifier. When a pattern matches (prefix/wildcard), it substitutes the captured part into each substitution target and tries the inner resolver with the raw specifier, then './<replaced>' and '<replaced>' relative to baseUrl. If every candidate fails, it bails with this message, embedding the per-target resolution errors, so the specifier matched a paths mapping but none of the mapped destinations exist.

Source

Thrown at crates/swc_ecma_loader/src/resolvers/tsc.rs:296

                            info!(
                                "Using `{}` for `{}` because the length of the jsc.paths entry is \
                                 1",
                                replaced, module_specifier
                            );
                            return Ok(Resolution {
                                slug: Some(
                                    replaced
                                        .split([std::path::MAIN_SEPARATOR, '/'])
                                        .next_back()
                                        .unwrap()
                                        .into(),
                                ),
                                filename: FileName::Real(replaced.into()),
                            });
                        }
                    }

                    bail!(
                        "`{module_specifier}` matched `{prefix}` (from tsconfig.paths) but failed \
                         to resolve:\n{errors:?}"
                    )
                }
                Pattern::Exact(from) => {
                    // Should be exactly matched
                    if module_specifier != from {
                        continue;
                    }

                    let tp = Path::new(&to[0]);
                    let slug = to[0]
                        .split([std::path::MAIN_SEPARATOR, '/'])
                        .next_back()
                        .filter(|&slug| slug != "index.ts" && slug != "index.tsx")
                        .map(|v| v.rsplit_once('.').map(|v| v.0).unwrap_or(v))
                        .map(From::from);

View on GitHub (pinned to 5176682b65)

Solutions

  1. Check each substitution target in the matched paths entry actually exists on disk relative to baseUrl (mind the './' prefixing and case sensitivity)
  2. Fix `baseUrl` in tsconfig so relative targets resolve from the intended root, or make targets absolute
  3. Add or correct the missing file the alias points to, or update the alias after directory renames
  4. Read the embedded per-target errors in the message: they tell you exactly which candidates the resolver attempted

Example fix

// before (tsconfig.json)
{ "baseUrl": "src", "paths": { "@/*": ["src/*"] } }
// '@/foo' -> tries src/src/foo -> fails

// after
{ "baseUrl": ".", "paths": { "@/*": ["src/*"] } }
// '@/foo' -> resolves ./src/foo.ts
Defensive patterns

Strategy: try-catch

Validate before calling

// Verify every substitution target of the matched alias exists relative to baseUrl
import { existsSync } from 'node:fs';
import path from 'node:path';
function aliasTargetsExist(tsconfig, spec) {
  for (const [pattern, targets] of Object.entries(tsconfig.compilerOptions.paths ?? {})) {
    const prefix = pattern.split('*')[0];
    if (spec.startsWith(prefix)) {
      const rest = spec.slice(prefix.length);
      const ok = targets.some((t) => existsSync(path.resolve(tsconfig.compilerOptions.baseUrl ?? '.', t.replace('*', rest))));
      if (!ok) return false;
    }
  }
  return true;
}

Try / catch

try {
  const resolved = tsResolver.resolve(base, spec);
} catch (e) {
  if (e.message.includes('(from tsconfig.paths)')) {
    // e.message lists every attempted target + inner errors; fix tsconfig or add the file
    throw new Error(`tsconfig paths misconfigured for '${spec}': ${e.message}`);
  }
  throw e;
}

Prevention

When it happens

Trigger: tsconfig contains `paths: { "@/*": ["src/legacy/*"] } }` and an import like '@/foo' matches, but src/legacy/foo.ts does not exist (wrong baseUrl, moved directory, wrong casing, or target file truly missing). The `to.len() == 1` fallback only returns the replaced path directly when there is exactly one target; otherwise failure is fatal.

Common situations: baseUrl not set or pointing to the wrong directory so './replaced' resolves from the wrong root; paths aliases from a template not updated after a folder rename; monorepos where tsconfig lives in a package root but the build runs from the workspace root; multiple substitution targets where none exists; extension not covered by the resolver's substitution table.

Related errors


AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17). Data as JSON: /api/errors/4e15619fee6435b3. Report an issue: GitHub.