How to replicate NPC movement from client to client

I have an NPC movement system in my personal project that almost runs entirely on the client. The NPCs are spawned on each client, that client handles their own pathfinding calculations. The only thing is I don’t know how to replicate the NPC’s movement from the client who owns the NPC to the clients who don’t.
I’ve looked into suphi kaner’s method, but it has the client calculating paths for every NPC in the game, even the ones they don’t own.
I wanted to ask if this is not as much a performance hit as I think, or give me an insight on how to implement this system.

--module

local module = {}
module.__index = module
local paths: {Path} = {}

local PathS = game:GetService("PathfindingService")
local RunS = game:GetService("RunService")
local TS = game:GetService("TweenService")

local function FindPath(pos1: Vector3, pos2: Vector3)
	if paths[tostring(pos1).."-"..tostring(pos2)] then print("Path already computed") return paths[tostring(pos1).."-"..tostring(pos2)] end
	local path = PathS:CreatePath({
		AgentCanClimb = false,
		AgentCanJump = false
	})
	
	
	local tries = 0
	
	while not paths[tostring(pos1).."-"..tostring(pos2)] do
		tries += 1
		local success, msg = pcall(function() path:ComputeAsync(pos1, pos2) end)
		
		if success and path.Status == Enum.PathStatus.Success then
			print("Calculated path after "..tries.. " tries")
			paths[tostring(pos1).."-"..tostring(pos2)] = path
			return paths[tostring(pos1).."-"..tostring(pos2)]
		elseif not success then
			warn("Path not computed: "..msg)
		end
		
		if tries >= 3 then
			warn("Exceeded 3 tries. Returning")
			return 
		end 
	end
end
function module.ClientSpawn(npc)
	setmetatable(npc, module)
	npc:Spawn()
end

function module.New(player)
	local Plot = require(game.ServerScriptService.PlotFunctions)
	local npc = require(game.ReplicatedStorage.Configs.NPCs)[RandomizeNPC()]
	local rarityTable = {"Common", "Uncommon", "Rare", "Very Rare", "Legendary", "Mythical"}
	local purchasePrefTable = {"Cheap", "Expensive"}
	local bindableEvent = Instance.new("BindableEvent")
	
	local self = setmetatable({},module)
	
	self.CustomerOf = player
	self.Money = math.random(npc.MinMoney, npc.MaxMoney)
	self.RarityPreferences = npc.RarityPreference
	self.PurchasePreferences = purchasePrefTable[math.random(1,#purchasePrefTable)]
	self.Model = npc.Model[math.random(1, #npc.Model)]
	self.Plot = Plot.GetPlot(player)
	
	
	for i = 1, #rarityTable, 1 do
		if table.find(self.RarityPreferences, rarityTable[i]) then table.remove(rarityTable,i) end
	end
	
	table.insert(self.RarityPreferences, rarityTable[math.random(1, #rarityTable)])
	
	game.ReplicatedStorage.Events.SpawnNPC:FireClient(player, self)
	
	return self
end

function module:Spawn()
	if RunS:IsClient() then
		local cloned = self.Model:Clone()
		cloned.Parent = self.Plot.NPCs
		self.Model = cloned
		cloned:PivotTo(CFrame.new(0,5,0))
		self:WalkIn()
	end
	
end

function module:WalkIn()
	if RunS:IsClient() then
		local path: Path = FindPath(CFrame.new(0,5,0).Position, self.Plot.NPCDestination.Position)
		local humanoid: Humanoid = self.Model.Humanoid

		Walk(humanoid, path, self.Plot.NPCDestination.CFrame.RightVector * -1)
		self:Wander()
	end
end

function module:Wander()
	local possibleDestinations = {}
	local maxDestinations = math.random(3,7)
	
	for _, item in self.Plot.StoreItems:GetChildren() do
		if item:FindFirstChild("NPCDestination") then table.insert(possibleDestinations, item.NPCDestination) end
	end
	
	while(#possibleDestinations > maxDestinations) do
		table.remove(possibleDestinations, math.random(1,#possibleDestinations))
	end
	
	local humanoid = self.Model.Humanoid
	
	possibleDestinations = RandomizeTable(possibleDestinations)
	
	for i,v in possibleDestinations do
		local origin 
		if i == 1 then origin = possibleDestinations[1] else origin = possibleDestinations[i - 1] end
		local path = FindPath(origin.Position, v.Position)
		
		Walk(humanoid, path, possibleDestinations[i].CFrame.RightVector * -1)

		task.wait(2)
	end
	
end

return module
-- server
local NPC = require(game.ReplicatedStorage.Modules.NPCSHandler)
local PS = game:GetService("Players")

while task.wait(4) do
	for _,v in PS:GetPlayers() do
		npc = NPC.New(v)
	end
end
--client
local NPC = require(game.ReplicatedStorage.Modules.NPCSHandler)

RS:WaitForChild("Events"):WaitForChild("SpawnNPC").OnClientEvent:Connect(function(npc)
	NPC.ClientSpawn(npc)
end)

I’m not an expert, and sorry if I misunderstood, but since your NPC system runs on the client, you’d need to use remote events every time the NPC updates its waypoint so other clients can see the movement. That means more network traffic and a higher chance of desync if updates are missed or delayed.

Doing everything on the server would avoid that, since the movement would be consistent for everyone, but it also puts more load on the server, especially with lots of NPCs. It’s really just a trade-off between client performance, network syncing and server performance.