diff --git a/src/DotRecast.Detour.Dynamic/DtDynamicNavMesh.cs b/src/DotRecast.Detour.Dynamic/DtDynamicNavMesh.cs
index 5c5a2531..07b58701 100644
--- a/src/DotRecast.Detour.Dynamic/DtDynamicNavMesh.cs
+++ b/src/DotRecast.Detour.Dynamic/DtDynamicNavMesh.cs
@@ -23,6 +23,7 @@ 3. This notice may not be removed or altered from any source distribution.
 using System.Linq;
 using System.Threading.Tasks;
 using DotRecast.Core;
+using DotRecast.Core.Collections;
 using DotRecast.Detour.Dynamic.Colliders;
 using DotRecast.Detour.Dynamic.Io;
 using DotRecast.Recast;
@@ -63,7 +64,7 @@ public DtDynamicNavMesh(DtVoxelFile voxelFile)
             navMeshParams.orig.Y = voxelFile.bounds[1];
             navMeshParams.orig.Z = voxelFile.bounds[2];
             navMeshParams.tileWidth = voxelFile.useTiles ? voxelFile.cellSize * voxelFile.tileSizeX : voxelFile.bounds[3] - voxelFile.bounds[0];
-            navMeshParams.tileHeight = voxelFile.useTiles ? voxelFile.cellSize * voxelFile.tileSizeZ: voxelFile.bounds[5] - voxelFile.bounds[2];
+            navMeshParams.tileHeight = voxelFile.useTiles ? voxelFile.cellSize * voxelFile.tileSizeZ : voxelFile.bounds[5] - voxelFile.bounds[2];
             navMeshParams.maxTiles = voxelFile.tiles.Count;
             navMeshParams.maxPolys = 0x8000;
             foreach (var t in voxelFile.tiles)
@@ -230,6 +231,8 @@ private bool UpdateNavMesh()
         {
             if (_dirty)
             {
+                _dirty = false;
+
                 DtNavMesh navMesh = new DtNavMesh();
                 navMesh.Init(navMeshParams, MAX_VERTS_PER_POLY);
 
@@ -239,7 +242,6 @@ private bool UpdateNavMesh()
                 }
 
                 _navMesh = navMesh;
-                _dirty = false;
                 return true;
             }
 
@@ -267,5 +269,21 @@ public List<RcBuilderResult> RecastResults()
         {
             return _tiles.Values.Select(t => t.recastResult).ToList();
         }
+
+        public void NavMesh(DtNavMesh mesh)
+        {
+            _tiles.Values.ForEach(t =>
+            {
+                const int MAX_NEIS = 32;
+                DtMeshTile[] tiles = new DtMeshTile[MAX_NEIS];
+                int nneis = mesh.GetTilesAt(t.voxelTile.tileX, t.voxelTile.tileZ, tiles, MAX_NEIS);
+                if (0 < nneis)
+                {
+                    t.SetMeshData(tiles[0].data);
+                }
+            });
+            _navMesh = mesh;
+            _dirty = false;
+        }
     }
 }
\ No newline at end of file
diff --git a/src/DotRecast.Detour.Dynamic/DtDynamicTile.cs b/src/DotRecast.Detour.Dynamic/DtDynamicTile.cs
index bf844375..17a15754 100644
--- a/src/DotRecast.Detour.Dynamic/DtDynamicTile.cs
+++ b/src/DotRecast.Detour.Dynamic/DtDynamicTile.cs
@@ -189,5 +189,10 @@ public void AddTo(DtNavMesh navMesh)
                 id = 0;
             }
         }
+
+        public void SetMeshData(DtMeshData data)
+        {
+            this.meshData = data;
+        }
     }
 }
\ No newline at end of file
diff --git a/test/DotRecast.Detour.Dynamic.Test/DynamicNavMeshTest.cs b/test/DotRecast.Detour.Dynamic.Test/DynamicNavMeshTest.cs
index 2f11aeee..deb60c33 100644
--- a/test/DotRecast.Detour.Dynamic.Test/DynamicNavMeshTest.cs
+++ b/test/DotRecast.Detour.Dynamic.Test/DynamicNavMeshTest.cs
@@ -1,3 +1,4 @@
+using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Threading.Tasks;
@@ -6,6 +7,7 @@
 using DotRecast.Detour.Dynamic.Colliders;
 using DotRecast.Detour.Dynamic.Io;
 using DotRecast.Detour.Dynamic.Test.Io;
+using DotRecast.Detour.Io;
 using NUnit.Framework;
 
 namespace DotRecast.Detour.Dynamic.Test;
@@ -78,4 +80,101 @@ public void E2eTest()
         // path length should be back to the initial value
         Assert.That(path.Count, Is.EqualTo(16));
     }
+
+
+    [Test]
+    public void ShouldSaveAndLoadDynamicNavMesh()
+    {
+        using var writerMs = new MemoryStream();
+        using var bw = new BinaryWriter(writerMs);
+
+
+        int maxVertsPerPoly = 6;
+        // load voxels from file
+
+        {
+            byte[] bytes = RcIO.ReadFileIfFound("test_tiles.voxels");
+            using var readMs = new MemoryStream(bytes);
+            using var br = new BinaryReader(readMs);
+
+            DtVoxelFileReader reader = new DtVoxelFileReader(DtVoxelTileLZ4ForTestCompressor.Shared);
+            DtVoxelFile f = reader.Read(br);
+
+            // create dynamic navmesh
+            DtDynamicNavMesh mesh = new DtDynamicNavMesh(f);
+
+            // build navmesh asynchronously using multiple threads
+            mesh.Build(Task.Factory);
+
+            // Save the resulting nav mesh and re-use it
+            new DtMeshSetWriter().Write(bw, mesh.NavMesh(), RcByteOrder.LITTLE_ENDIAN, true);
+            maxVertsPerPoly = mesh.NavMesh().GetMaxVertsPerPoly();
+        }
+
+        {
+            byte[] bytes = RcIO.ReadFileIfFound("test_tiles.voxels");
+            using var readMs = new MemoryStream(bytes);
+            using var br = new BinaryReader(readMs);
+
+            // load voxels from file
+            DtVoxelFileReader reader = new DtVoxelFileReader(DtVoxelTileLZ4ForTestCompressor.Shared);
+            DtVoxelFile f = reader.Read(br);
+
+            // create dynamic navmesh
+            DtDynamicNavMesh mesh = new DtDynamicNavMesh(f);
+            // use the saved nav mesh instead of building from scratch
+            DtNavMesh navMesh = new DtMeshSetReader().Read(new RcByteBuffer(writerMs.ToArray()), maxVertsPerPoly);
+            mesh.NavMesh(navMesh);
+
+            DtNavMeshQuery query = new DtNavMeshQuery(mesh.NavMesh());
+            IDtQueryFilter filter = new DtQueryDefaultFilter();
+
+            // find path
+            _ = query.FindNearestPoly(START_POS, EXTENT, filter, out var startNearestRef, out var startNearestPos, out var _);
+            _ = query.FindNearestPoly(END_POS, EXTENT, filter, out var endNearestRef, out var endNearestPos, out var _);
+
+            List<long> path = new List<long>();
+            query.FindPath(startNearestRef, endNearestRef, startNearestPos, endNearestPos, filter, ref path, DtFindPathOption.AnyAngle);
+
+            // check path length without any obstacles
+            Assert.That(path.Count, Is.EqualTo(16));
+
+            // place obstacle
+            DtCollider colldier = new DtSphereCollider(SPHERE_POS, 20, SampleAreaModifications.SAMPLE_POLYAREA_TYPE_GROUND, 0.1f);
+            long colliderId = mesh.AddCollider(colldier);
+
+            // update navmesh asynchronously
+            mesh.Update(Task.Factory);
+
+            // create new query
+            query = new DtNavMeshQuery(mesh.NavMesh());
+
+            // find path again
+            _ = query.FindNearestPoly(START_POS, EXTENT, filter, out startNearestRef, out startNearestPos, out var _);
+            _ = query.FindNearestPoly(END_POS, EXTENT, filter, out endNearestRef, out endNearestPos, out var _);
+
+            path = new List<long>();
+            query.FindPath(startNearestRef, endNearestRef, startNearestPos, endNearestPos, filter, ref path, DtFindPathOption.AnyAngle);
+
+            // check path length with obstacles
+            Assert.That(path.Count, Is.EqualTo(19));
+
+            // remove obstacle
+            mesh.RemoveCollider(colliderId);
+            // update navmesh asynchronously
+            mesh.Update(Task.Factory);
+
+            // create new query
+            query = new DtNavMeshQuery(mesh.NavMesh());
+            // find path one more time
+            _ = query.FindNearestPoly(START_POS, EXTENT, filter, out startNearestRef, out startNearestPos, out var _);
+            _ = query.FindNearestPoly(END_POS, EXTENT, filter, out endNearestRef, out endNearestPos, out var _);
+
+            path = new List<long>();
+            query.FindPath(startNearestRef, endNearestRef, startNearestPos, endNearestPos, filter, ref path, DtFindPathOption.AnyAngle);
+
+            // path length should be back to the initial value
+            Assert.That(path.Count, Is.EqualTo(16));
+        }
+    }
 }
\ No newline at end of file