1
5 Comments

Adding "Custom Slugs" to my SaaS seemed easy... until I realized the edge cases. 🤯

Adding custom links to NanoURL was supposed to be a quick weekend feature: just add a text input, check if the string exists in the database, and save it. Right?

Wrong. Building this feature for scale revealed a massive iceberg of edge cases. Here is how I ended up architecting it to prevent abuse and race conditions:

  1. The "Race Condition" Problem
    What happens if two users try to claim nanourl.link/summer-sale at the exact same millisecond?
    Solution: I couldn't rely on a simple existsByShortCode Java check. I had to let PostgreSQL do the heavy lifting with a UNIQUE constraint. If two requests hit the DB at once, the backend catches the DataIntegrityViolationException and translates it into a friendly UI error.

  2. The Cooldown & "Self-Lockout" Bug
    To stop bots from sniping deleted dictionary words, I built a 30-day "Cooldown" table. When a link is deleted, it goes into quarantine.
    The Edge Case: I realized that if a user accidentally deleted their own custom link and tried to recreate it, they would lock themselves out for 30 days!
    Solution: I tied the Cooldown record to the workspaceId. Now, the system blocks everyone else, but lets the original owner bypass the cooldown to reclaim their own slug.

  3. The "Hybrid Quota" Architecture
    Billing logic is hard. I didn't want free users hoarding dictionary words.
    Solution: I built a hybrid system. Free users are capped by Active Concurrent Links (e.g., max 5 alive at once. Want a 6th? Delete an old one). Paid users are capped by a Monthly Creation Limit. I manage this via a dynamic Key-Value plan_features table, so I can adjust pricing limits in the future without deploying new code.

"Simple" features rarely are. I’m pretty exhausted but incredibly proud of how robust the backend logic turned out.

How do you handle quotas and resource hoarding in your own apps? Do you prefer hard caps or monthly limits?

posted to Icon for group Building in Public
Building in Public
on April 14, 2026
  1. 1

    This is a great example of where product decisions and backend architecture are tightly coupled.
    The ‘self-lockout’ edge case is subtle but impactful—without that fix, you’d create frustration for your best users, not bad actors.
    Also really like the hybrid quota model. Active limits for free users + creation limits for paid feels like a smart way to balance abuse prevention with perceived value.
    Out of curiosity—have you thought about turning premium slugs (like dictionary words) into a separate tier or marketplace?

    1. 1

      The 'self-lockout' scenario highlights a critical UX consideration often overlooked.
      I have not thought about premium slugs model you mentioned. Will have to think about that. Thanks!"

  2. 1

    The self-lockout bug is such a classic example of features that seem obvious in hindsight but nobody catches until a real user hits it. Good catch on tying the cooldown to workspaceId.

    I ran into something similar with deep links in one of my apps. Thought it would be straightforward routing, then spent way too long dealing with reserved paths conflicting with user-generated slugs. The fix was boring (a blocklist of reserved prefixes checked at creation time) but the bug reports before that were not.

    The race condition approach is right. Letting the database handle uniqueness instead of checking in application code is one of those lessons that saves you from subtle bugs that only show up under load. I've seen people do the "check then insert" pattern and wonder why they get duplicates in production.

    One thing worth considering: Unicode normalization in slugs. If you allow international characters, you can end up with visually identical slugs that are technically different byte sequences. Might not matter for your use case, but it bit me once and I never forgot it.

    1. 1

      The lesson you learned about Unicode normalization in slugs is also a crucial one, as it can easily lead to issues with visually identical slugs. Thanks for sharing your learnings.

  3. 1

    Solid engineering post.

Trending on Indie Hackers
I built a tool that shows what a contract could cost you before signing User Avatar 113 comments The coordination tax: six years watching a one-day feature take four months User Avatar 74 comments My users are making my product better without knowing it. Here's how I designed that. User Avatar 64 comments A simple LinkedIn prospecting trick that improved our lead quality User Avatar 52 comments I changed AIagent2 from dashboard-first to chat-first. Does this feel clearer? User Avatar 39 comments Why I built a SaaS for online front-end projects that need more than a playground User Avatar 17 comments