Development Tenets for customers.dev

These core principles guide every decision we make in building and maintaining the customers.dev platform. They are not aspirational - they are requirements.

1. Real-Time First 🚀

We build for the speed of thought.

  • Everything should be as near to real-time as possible
  • Use Convex real-time subscriptions for data that isn't natively real-time
  • User actions should have immediate visual feedback
  • Background processes should use optimistic updates
  • WebSocket connections over polling when feasible

2. Fail Gracefully, Never Silently 🛡️

Every edge case is a user's reality.

  • Handle loading states explicitly (no blank screens)
  • Account for race conditions (auth initialization, webhook delays)
  • Provide meaningful error messages that guide users to solutions
  • Always have a fallback (if Clerk fails, if webhooks are delayed, if APIs timeout)
  • Log errors comprehensively but never expose sensitive data

3. Type Safety is Non-Negotiable 📋

If it compiles, it should work.

  • TypeScript strict mode always on
  • No any types without explicit justification
  • Define interfaces for all data structures
  • Test TypeScript compilation locally before deploying
  • Prefer explicit interfaces over conditional types

4. Developer Experience is User Experience 👩‍💻

The best code is code that developers love to work with.

  • Clear, semantic naming (no abbreviations unless universal)
  • Self-documenting code over extensive comments
  • Consistent patterns across the codebase
  • Git commits that tell a story
  • Fast local development with hot reload
  • Comprehensive development documentation

5. Performance from Day One ⚡

Speed is a feature, not an optimization.

  • Measure performance impact before merging
  • Lazy load what you can
  • Bundle size matters - check it
  • Optimize images and assets
  • Use proper caching strategies
  • Server-side render when it improves UX

6. Security by Default 🔒

Trust no input, verify everything.

  • All user input must be validated
  • Authentication and authorization on every protected route
  • Webhooks must be verified
  • API keys and secrets in environment variables only
  • Regular security audits
  • Principle of least privilege for all roles

7. Don't Repeat Yourself (DRY) 🔄

Write once, use everywhere.

  • Shared components over duplicated code
  • Centralized configuration
  • Reusable utilities and hooks
  • Single source of truth for business logic
  • Extract patterns when you see them repeated 3+ times

8. Progressive Enhancement 📈

Start simple, enhance intelligently.

  • Core functionality works without JavaScript
  • Features degrade gracefully
  • Mobile-first responsive design
  • Accessibility from the start, not as an afterthought
  • Support users on slower connections

9. Ship Small, Ship Often 🚢

Perfect is the enemy of shipped.

  • Small, focused pull requests
  • Feature flags for gradual rollouts
  • Continuous deployment
  • Quick iterations based on user feedback
  • Rollback strategy for every deployment

10. User Trust is Sacred 🤝

Every interaction should build confidence.

  • Transparent about system status
  • Clear about data usage
  • Honest about limitations
  • Quick to acknowledge and fix issues
  • Proactive communication during outages

11. Data Integrity Above All 💾

Lost data loses users.

  • ACID compliance where it matters
  • Backup strategies for critical data
  • Audit trails for important operations
  • Data validation at every layer
  • Clear data retention policies

12. Observability is Mandatory 📊

You can't fix what you can't see.

  • Structured logging with context
  • Performance monitoring on key metrics
  • Error tracking with proper grouping
  • User behavior analytics (privacy-respecting)
  • Real-time alerts for critical issues

Applying These Tenets

When making decisions, ask yourself:

  1. Does this make the user experience more real-time?
  2. Have I handled all the ways this could fail?
  3. Is this type-safe and will it catch errors at compile time?
  4. Would I enjoy maintaining this code in 6 months?
  5. Will this perform well at scale?
  6. Is this secure by default?
  7. Am I repeating code that already exists?
  8. Does this work for all users, not just the ideal case?
  9. Can I ship this incrementally?
  10. Does this build or erode user trust?
  11. Is the data safe and validated?
  12. Will I know if this breaks in production?

Examples in Practice

✅ Good: Webhook Race Condition Handling

// Assume new users without roles are waitlist (webhook hasn't fired yet)
const isWaitlistUser = userRoles.length === 0 || (userRoles.length === 1 && userRoles[0] === "waitlist")

❌ Bad: Silent Failures

// Don't do this
try {
  await saveUser(data)
} catch (e) {
  // Silent failure - violates Tenet #2
}

✅ Good: Type-Safe Interfaces

interface AuthUser {
  id: string
  email: string
  roles: string[]
  // ... other fields
}

❌ Bad: Using Any

// Don't do this
const processData = (data: any) => {
  // Violates Tenet #3
}

Living Document

These tenets evolve with our understanding. If you find yourself consistently violating a tenet, either:

  1. You're doing something wrong, or
  2. The tenet needs updating

Discuss with the team before changing core tenets.

On this page