11/24/2025
TLDR: Broken links break customer trust and scattered URL logic guarantees you'll ship broken links. Centralized URL utilities (one file, not five) let you change domains without disaster. This architectural choice is the difference between debugging production fires and confident deployments.
Broken links break more than customer trust.
Here is an example of a user experience with a new service:
User = This product sucks
Product Manager: Why are some links working for customers and others aren’t?
Engineer: Oh no…I didn’t update that one file
There are two approaches: Scattered vs Centralized logic
Scattered logic: Your codebase has URL generation in 5+ different files:
File: RegistrationDialog.tsx
└─ Generates: window.location.origin + '/event/' + id
File: Ticket.tsx
└─ Generates: window.location.origin + '/ticket/' + code
File: Waitlist.tsx
└─ Generates: window.location.origin + '/waitlist/' + code
File: EmailService.tsx
└─ Generates: window.location.origin + '/event/' + id
File: QRCodeGenerator.tsx
└─ Generates: window.location.origin + '/ticket/' + code
Problem with this approach?
When you need to change how URLs work (e.g. app.domain.com → domain.com)
Results? Users see broken links, missed a few routes, spend hours debugging
✅Update registrationDialog.tsx → registration links work
✅Update ticket.tsx → ticket links work
❌Forgot to update emailService.tsx → email links broken
❌Forgot to update QRcodeGenerator.tsx → QR codes still point to old domain
Centralized: You have ONE file that handles ALL URL generation:
File: urls.ts
├─ getEventUrl(id) → Returns correct event URL
├─ getTicketUrl(code) → Returns correct ticket URL
└─ getWaitlistUrl(code) → Returns correct waitlist URL
Then EVERY other file imports from this utility:
File: RegistrationDialog.tsx
└─ import { getEventUrl } from '@/utils/urls'
File: Ticket.tsx
└─ import { getTicketUrl } from '@/utils/urls'
File: Waitlist.tsx
└─ import { getWaitlistUrl } from '@/utils/urls'
File: EmailService.tsx
└─ import { getEventUrl } from '@/utils/urls'
File: QRCodeGenerator.tsx
└─ import { getTicketUrl } from '@/utils/urls'
Why is this a better approach?
When you need to change how URLs work (e.g. app.domain.com → domain.com)
Results? All links work. Users happy. Deploy successful.
The question to ask your team next week: "Where does our URL generation logic live? Is it centralized or scattered?"
If the answer is scattered across multiple files, you're carrying a hidden risk. If it's centralized, you've built for scale.