Broken links break more than customer trust

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:

  1. You sign up for a service
  2. Onboarding goes well, everything looks good
  3. You go to share a link → broken
  4. Or you scan a QR code → 404 error
  5. Or you click “Copy link” → link doesn’t work

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

Single source of truth for URL generation

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.comdomain.com)

  1. Remember all 5+ places URLs are generated
  2. Update each file individually
  3. Hope you don’t miss any
  4. Deploy and pray

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.comdomain.com)

  1. Update one file (urls.ts)
  2. Every part of the app automatically uses the new logic
  3. Deploy with confidence

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.

Back to Blog