I’ve recently been using Zod to validate data in Remix projects.
My main rule of thumb: keep Zod on the server.
Since you have full control over what data is passed from Remix’s loader() or action() to the client, you can trust that the data will be in the expected format by the time it reaches the browser when you validate with Zod on the server.
For example, this is a common pattern for validating URL parameters in a Remix loader().
I do something similar in my action() functions, but I’m usually interested in the FormData that gets submitted in a form submission or other action from the client.