diff --git a/Cargo.lock b/Cargo.lock
index f2fc808f..c1544af9 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -307,20 +307,6 @@ dependencies = [
  "syn",
 ]
 
-[[package]]
-name = "dashmap"
-version = "6.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf"
-dependencies = [
- "cfg-if",
- "crossbeam-utils",
- "hashbrown 0.14.5",
- "lock_api",
- "once_cell",
- "parking_lot_core",
-]
-
 [[package]]
 name = "deranged"
 version = "0.3.11"
@@ -403,12 +389,6 @@ version = "0.12.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
 
-[[package]]
-name = "hashbrown"
-version = "0.14.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
-
 [[package]]
 name = "hashbrown"
 version = "0.15.2"
@@ -516,7 +496,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
 dependencies = [
  "cfg-if",
- "windows-targets 0.52.6",
+ "windows-targets 0.48.5",
 ]
 
 [[package]]
@@ -536,16 +516,6 @@ version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5"
 
-[[package]]
-name = "lock_api"
-version = "0.4.12"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
-dependencies = [
- "autocfg",
- "scopeguard",
-]
-
 [[package]]
 name = "log"
 version = "0.4.22"
@@ -681,15 +651,16 @@ version = "3.0.3"
 dependencies = [
  "cfg-if",
  "criterion2",
- "dashmap",
  "document-features",
  "indexmap 2.7.0",
  "json-strip-comments",
  "normalize-path",
  "once_cell",
+ "papaya",
  "pnp",
  "rayon",
  "rustc-hash",
+ "seize",
  "serde",
  "serde_json",
  "simdutf8",
@@ -699,16 +670,13 @@ dependencies = [
 ]
 
 [[package]]
-name = "parking_lot_core"
-version = "0.9.10"
+name = "papaya"
+version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
+checksum = "dc7c76487f7eaa00a0fc1d7f88dc6b295aec478d11b0fc79f857b62c2874124c"
 dependencies = [
- "cfg-if",
- "libc",
- "redox_syscall",
- "smallvec",
- "windows-targets 0.52.6",
+ "equivalent",
+ "seize",
 ]
 
 [[package]]
@@ -863,10 +831,14 @@ dependencies = [
 ]
 
 [[package]]
-name = "scopeguard"
-version = "1.2.0"
+name = "seize"
+version = "0.4.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
+checksum = "d84b0c858bdd30cb56f5597f8b3bf702ec23829e652cc636a1e5a7b9de46ae93"
+dependencies = [
+ "libc",
+ "windows-sys 0.52.0",
+]
 
 [[package]]
 name = "semver"
@@ -1196,6 +1168,15 @@ dependencies = [
  "windows-targets 0.48.5",
 ]
 
+[[package]]
+name = "windows-sys"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+dependencies = [
+ "windows-targets 0.52.6",
+]
+
 [[package]]
 name = "windows-sys"
 version = "0.59.0"
diff --git a/Cargo.toml b/Cargo.toml
index 4b42ac59..35f136e7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -64,11 +64,12 @@ name = "resolver"
 
 [dependencies]
 cfg-if = "1"
-dashmap = { version = "6", features = ["raw-api"] }
 indexmap = { version = "2", features = ["serde"] }
 json-strip-comments = "1"
 once_cell = "1" # Use `std::sync::OnceLock::get_or_try_init` when it is stable.
+papaya = "0.1.8"
 rustc-hash = { version = "2" }
+seize = { version = "0.4" }
 serde = { version = "1", features = ["derive"] } # derive for Deserialize from package.json
 serde_json = { version = "1", features = ["preserve_order"] } # preserve_order: package_json.exports requires order such as `["require", "import", "default"]`
 simdutf8 = { version = "0.1" }
diff --git a/src/fs_cache.rs b/src/fs_cache.rs
index 615754c7..ff73c24e 100644
--- a/src/fs_cache.rs
+++ b/src/fs_cache.rs
@@ -13,8 +13,8 @@ use std::{
 };
 
 use cfg_if::cfg_if;
-use dashmap::{DashMap, DashSet};
 use once_cell::sync::OnceCell as OnceLock;
+use papaya::{Equivalent, HashMap, HashSet};
 use rustc_hash::FxHasher;
 
 use crate::{
@@ -38,50 +38,16 @@ thread_local! {
 #[derive(Default)]
 pub struct FsCache<Fs> {
     pub(crate) fs: Fs,
-    paths: DashSet<PathEntry<'static>, BuildHasherDefault<IdentityHasher>>,
-    tsconfigs: DashMap<PathBuf, Arc<TsConfig>, BuildHasherDefault<FxHasher>>,
+    paths: HashSet<FsCachedPath, BuildHasherDefault<IdentityHasher>>,
+    tsconfigs: HashMap<PathBuf, Arc<TsConfig>, BuildHasherDefault<FxHasher>>,
 }
 
-/// An entry in the path cache. Can also be borrowed for lookups without allocations.
-enum PathEntry<'a> {
-    Owned(FsCachedPath),
-    Borrowed { hash: u64, path: &'a Path },
-}
-
-impl Hash for PathEntry<'_> {
-    fn hash<H: Hasher>(&self, state: &mut H) {
-        match self {
-            PathEntry::Owned(entry) => {
-                entry.hash.hash(state);
-            }
-            PathEntry::Borrowed { hash, .. } => {
-                hash.hash(state);
-            }
-        }
-    }
-}
-
-impl PartialEq for PathEntry<'_> {
-    fn eq(&self, other: &Self) -> bool {
-        let self_path = match self {
-            PathEntry::Owned(info) => &info.path,
-            PathEntry::Borrowed { path, .. } => *path,
-        };
-        let other_path = match other {
-            PathEntry::Owned(info) => &info.path,
-            PathEntry::Borrowed { path, .. } => *path,
-        };
-        self_path.as_os_str() == other_path.as_os_str()
-    }
-}
-impl Eq for PathEntry<'_> {}
-
 impl<Fs: FileSystem> Cache for FsCache<Fs> {
     type Cp = FsCachedPath;
 
     fn clear(&self) {
-        self.paths.clear();
-        self.tsconfigs.clear();
+        self.paths.pin().clear();
+        self.tsconfigs.pin().clear();
     }
 
     #[allow(clippy::cast_possible_truncation)]
@@ -93,17 +59,9 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> {
             path.as_os_str().hash(&mut hasher);
             hasher.finish()
         };
-        let key = PathEntry::Borrowed { hash, path };
-        // A DashMap is just an array of RwLock<HashSet>, sharded by hash to reduce lock contention.
-        // This uses the low level raw API to avoid cloning the value when using the `entry` method.
-        // First, find which shard the value is in, and check to see if we already have a value in the map.
-        let shard = self.paths.determine_shard(hash as usize);
-        {
-            // Scope the read lock.
-            let map = self.paths.shards()[shard].read();
-            if let Some((PathEntry::Owned(entry), _)) = map.get(hash, |v| v.0 == key) {
-                return entry.clone();
-            }
+        let paths = self.paths.pin();
+        if let Some(entry) = paths.get(&BorrowedCachedPath { hash, path }) {
+            return entry.clone();
         }
         let parent = path.parent().map(|p| self.value(p));
         let cached_path = FsCachedPath(Arc::new(CachedPathImpl::new(
@@ -111,7 +69,7 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> {
             path.to_path_buf().into_boxed_path(),
             parent,
         )));
-        self.paths.insert(PathEntry::Owned(cached_path.clone()));
+        paths.insert(cached_path.clone());
         cached_path
     }
 
@@ -196,8 +154,9 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> {
         path: &Path,
         callback: F, // callback for modifying tsconfig with `extends`
     ) -> Result<Arc<TsConfig>, ResolveError> {
-        if let Some(tsconfig) = self.tsconfigs.get(path) {
-            return Ok(Arc::clone(&tsconfig));
+        let tsconfigs = self.tsconfigs.pin();
+        if let Some(tsconfig) = tsconfigs.get(path) {
+            return Ok(Arc::clone(tsconfig));
         }
         let meta = self.fs.metadata(path).ok();
         let tsconfig_path = if meta.is_some_and(|m| m.is_file) {
@@ -219,14 +178,26 @@ impl<Fs: FileSystem> Cache for FsCache<Fs> {
             })?;
         callback(&mut tsconfig)?;
         let tsconfig = Arc::new(tsconfig.build());
-        self.tsconfigs.insert(path.to_path_buf(), Arc::clone(&tsconfig));
+        tsconfigs.insert(path.to_path_buf(), Arc::clone(&tsconfig));
         Ok(tsconfig)
     }
 }
 
 impl<Fs: FileSystem> FsCache<Fs> {
     pub fn new(fs: Fs) -> Self {
-        Self { fs, paths: DashSet::default(), tsconfigs: DashMap::default() }
+        Self {
+            fs,
+            paths: HashSet::builder()
+                .hasher(BuildHasherDefault::default())
+                .resize_mode(papaya::ResizeMode::Blocking)
+                .collector(seize::Collector::new().epoch_frequency(None))
+                .build(),
+            tsconfigs: HashMap::builder()
+                .hasher(BuildHasherDefault::default())
+                .resize_mode(papaya::ResizeMode::Blocking)
+                .collector(seize::Collector::new().epoch_frequency(None))
+                .build(),
+        }
     }
 
     /// Returns the canonical path, resolving all symbolic links.
@@ -479,6 +450,29 @@ impl PartialEq for FsCachedPath {
 
 impl Eq for FsCachedPath {}
 
+struct BorrowedCachedPath<'a> {
+    hash: u64,
+    path: &'a Path,
+}
+
+impl Equivalent<FsCachedPath> for BorrowedCachedPath<'_> {
+    fn equivalent(&self, other: &FsCachedPath) -> bool {
+        self.path.as_os_str() == other.path.as_os_str()
+    }
+}
+
+impl Hash for BorrowedCachedPath<'_> {
+    fn hash<H: Hasher>(&self, state: &mut H) {
+        self.hash.hash(state);
+    }
+}
+
+impl PartialEq for BorrowedCachedPath<'_> {
+    fn eq(&self, other: &Self) -> bool {
+        self.path.as_os_str() == other.path.as_os_str()
+    }
+}
+
 /// Since the cache key is memoized, use an identity hasher
 /// to avoid double cache.
 #[derive(Default)]