Slug hygiene: short links people can say out loud
A slug lives two lives. Most of the time it's machine-handled — clicked, scanned, copy-pasted — and nothing below matters. But the moment a human has to transmit one (read it on a podcast, dictate it over the phone, type it off a slide across the room), five small decisions decide whether it arrives intact.
1 · alphabet: ban the impostors
The classic confusion pairs — 0/O, 1/l/I, and in some fonts 5/S and 8/B — are responsible for a comic share of "your link is broken" emails where the link was fine and the transcription wasn't. Our random slugs are drawn from an alphabet with the lookalikes removed, which is why you'll never be issued l0lO1. If you're choosing a vanity slug by hand, apply the same ban manually.
2 · case: there is only lowercase
Mixed-case slugs double your collision space and quadruple your dictation problems ("capital B, lowercase r — no, capital"). Every slug here is lowercase, full stop, and anything typed in uppercase folds down to it. The URL spec may permit case-sensitive paths; the human voice does not.
3 · length: short enough to say, long enough to last
Five random characters from a 31-character alphabet is ~28 million combinations — comfortable headroom without becoming a mouthful. Vanity slugs can go longer, but past ~12 characters you're re-creating the problem short links exist to solve. If a slug needs a hyphen to stay readable, it's allowed exactly one good one: spring-sale reads aloud; spr-sl-26-v2 is a cry for help.
4 · meaning: name the thing, not the moment
The best vanity slugs describe the stable purpose, not the current contents — because the contents are what you'll change later. /menu outlives /october-menu; /release-notes outlives /v2-4-1-notes. A slug named after the moment fossilizes it; a slug named after the role can be repointed forever without lying. (This pairs with everything in the status-code guide: a role-named slug on a 307 is the most editable object on the internet.)
5 · retirement: links deserve funerals
A retired slug pointed at nothing is a little landmine for whoever clicks it in an old email three years from now. Give campaigns an expires_at and a fallback_url at creation time — a "this one's over, here's the current thing" page costs nothing and reads as basic competence. Deleting outright still gets a 30-day fallback here, but deliberate retirement beats default behavior every time.
The checklist: no lookalike characters · lowercase only · ≤12 chars, ≤1 hyphen · named for the role, not the moment · retired to a destination, never to a void. Five decisions, zero support tickets.