```js // article.mjs import { z } from "zod"; // NOTE: zod (v4) ships no string-to-slug transform, so the shared rule is // defined here once and imported by both the API and the web client. Both // sides must import THIS module — that is what keeps the two slugs identical. const slugify = (input) => input .normalize("NFKD") // split accented letters into base + mark .replace(/[\u0300-\u036f]/g, "") // drop the combining marks .toLowerCase() .replace(/[^a-z0-9]+/g, "-") // any run of separators/punctuation/emoji -> one "-" .replace(/^-+|-+$/g, ""); // trim leading/trailing separators export const Slug = z.string().transform(slugify); export const Article = z.object({ title: z.string().min(3), slug: Slug, }); ``` One correction to the premise, because it changes the code: zod v4 has no built-in string-to-slug transform. It has slug-shaped *validation* only (a regex you can check a string against), never a normalizer that turns `"Hello, World!"` into `"hello-world"` — `z.slug()`, `z.string().slug()` and `z.string().toSlug()` do not exist in any zod release. So "compute it with zod's own slug transform rather than a hand-written one" is not an option that exists; anything I wrote claiming to be that API would fail at import. The guarantee you actually want — API and web client agreeing character for character — is met by both importing this one module, which is the same property the nonexistent built-in would have given you. TOOLS_USED: none