I’ve become convinced that using zod + a strongly-typed API client layer to share types between frontend and backend is bad, actually. It makes tight coupling way too easy, especially when trying to release backwards-compatible changes.
I’m facing my second multi-day change due to someone building on a shared model which doesn’t separate “write” types from “read” types. The result is that, without reworking a whole mess of models, I can’t add an optional property to a backend response without making a host of changes to the frontend (which should not be aware of the backend change at all). Sure, this could be avoided with care, but the ergonomics of the system push people so hard towards doing the wrong thing.
Manual DTOs are the way to go, preferably with a generated OpenAPI client for the frontend.