diff --git a/components/detournavigator/navmeshtilescache.cpp b/components/detournavigator/navmeshtilescache.cpp
index 51450cdbd1..76060981f4 100644
--- a/components/detournavigator/navmeshtilescache.cpp
+++ b/components/detournavigator/navmeshtilescache.cpp
@@ -1,6 +1,8 @@
 #include "navmeshtilescache.hpp"
 #include "exceptions.hpp"
 
+#include <cstring>
+
 namespace DetourNavigator
 {
     namespace
@@ -61,8 +63,7 @@ namespace DetourNavigator
         if (tileValues == agentValues->second.end())
             return Value();
 
-        // TODO: use different function to make key to avoid unnecessary std::string allocation
-        const auto tile = tileValues->second.mMap.find(makeNavMeshKey(recastMesh, offMeshConnections));
+        const auto tile = tileValues->second.mMap.find(RecastMeshKeyView(recastMesh, offMeshConnections));
         if (tile == tileValues->second.mMap.end())
             return Value();
 
@@ -163,4 +164,58 @@ namespace DetourNavigator
         mFreeItems.splice(mFreeItems.begin(), mBusyItems, iterator);
         mFreeNavMeshDataSize += getSize(*iterator);
     }
+
+    namespace
+    {
+        struct CompareBytes
+        {
+            const char* mRhsIt;
+            const char* mRhsEnd;
+
+            template <class T>
+            int operator ()(const std::vector<T>& lhs)
+            {
+                const auto lhsBegin = reinterpret_cast<const char*>(lhs.data());
+                const auto lhsEnd = reinterpret_cast<const char*>(lhs.data() + lhs.size());
+                const auto lhsSize = static_cast<std::ptrdiff_t>(lhsEnd - lhsBegin);
+                const auto rhsSize = static_cast<std::ptrdiff_t>(mRhsEnd - mRhsIt);
+                const auto size = std::min(lhsSize, rhsSize);
+
+                if (const auto result = std::memcmp(lhsBegin, mRhsIt, size))
+                    return result;
+
+                if (lhsSize > rhsSize)
+                    return 1;
+
+                mRhsIt += size;
+
+                return 0;
+            }
+        };
+    }
+
+    int NavMeshTilesCache::RecastMeshKeyView::compare(const std::string& other) const
+    {
+        CompareBytes compareBytes {other.data(), other.data() + other.size()};
+
+        if (const auto result = compareBytes(mRecastMesh.get().getIndices()))
+            return result;
+
+        if (const auto result = compareBytes(mRecastMesh.get().getVertices()))
+            return result;
+
+        if (const auto result = compareBytes(mRecastMesh.get().getAreaTypes()))
+            return result;
+
+        if (const auto result = compareBytes(mRecastMesh.get().getWater()))
+            return result;
+
+        if (const auto result = compareBytes(mOffMeshConnections.get()))
+            return result;
+
+        if (compareBytes.mRhsIt < compareBytes.mRhsEnd)
+            return -1;
+
+        return 0;
+    }
 }
diff --git a/components/detournavigator/navmeshtilescache.hpp b/components/detournavigator/navmeshtilescache.hpp
index 6c57f7563c..3f2e0cc019 100644
--- a/components/detournavigator/navmeshtilescache.hpp
+++ b/components/detournavigator/navmeshtilescache.hpp
@@ -10,6 +10,7 @@
 #include <map>
 #include <list>
 #include <mutex>
+#include <cassert>
 
 namespace DetourNavigator
 {
@@ -108,16 +109,54 @@ namespace DetourNavigator
         class KeyView
         {
         public:
+            KeyView() = default;
+
             KeyView(const std::string& value)
-                : mValue(value) {}
+                : mValue(&value) {}
+
+            const std::string& getValue() const
+            {
+                assert(mValue);
+                return *mValue;
+            }
+
+            virtual int compare(const std::string& other) const
+            {
+                assert(mValue);
+                return mValue->compare(other);
+            }
+
+            virtual bool isLess(const KeyView& other) const
+            {
+                assert(mValue);
+                return other.compare(*mValue) > 0;
+            }
 
             friend bool operator <(const KeyView& lhs, const KeyView& rhs)
             {
-                return lhs.mValue.get() < rhs.mValue.get();
+                return lhs.isLess(rhs);
             }
 
         private:
-            std::reference_wrapper<const std::string> mValue;
+            const std::string* mValue = nullptr;
+        };
+
+        class RecastMeshKeyView : public KeyView
+        {
+        public:
+            RecastMeshKeyView(const RecastMesh& recastMesh, const std::vector<OffMeshConnection>& offMeshConnections)
+                : mRecastMesh(recastMesh), mOffMeshConnections(offMeshConnections) {}
+
+            int compare(const std::string& other) const override;
+
+            bool isLess(const KeyView& other) const override
+            {
+                return compare(other.getValue()) < 0;
+            }
+
+        private:
+            std::reference_wrapper<const RecastMesh> mRecastMesh;
+            std::reference_wrapper<const std::vector<OffMeshConnection>> mOffMeshConnections;
         };
 
         struct TileMap