Nobody tells you which decisions are cheap to change and which ones are expensive. You find out the hard way, six months in, when a customer asks for SSO and you realize your auth layer is a mess of custom session logic you cannot safely touch. Or when billing comes up and your codebase has no concept of a plan, a subscription, or a user limit anywhere.
The five decisions below fall into the “expensive to change” category. They take one to three days to implement correctly in week one. Retrofitting them into a mature codebase takes two to six weeks each, plus the bugs introduced during the migration. Make them right at the start.
Decision 1: Multi-Tenancy From Day One
If you are building a B2B SaaS product, every piece of data in your database belongs to an organization. This sounds obvious. Most early-stage codebases do not actually enforce it.
There are three common approaches:
- Separate databases per tenant: Maximum isolation, maximum complexity, maximum cost. Reserved for regulated industries like healthcare or finance where data isolation is a legal requirement, not just a preference.
- Separate schemas per tenant: Better isolation than row-level security, still manageable at small scale. Gets painful above 200 tenants when you are running migrations across thousands of schemas.
- Row-level security (RLS) with
organization_id: The right default for 90 percent of SaaS products. Every table that holds tenant data gets anorganization_idcolumn. Postgres RLS policies enforce that users can only read and write rows that belong to their organization. One database, clean isolation, straightforward migrations.
The mistake: building without any multi-tenancy model and retrofitting it later. When your first enterprise prospect asks “how is my data isolated from other customers,” you need a real answer. “We filter by user ID” is not that answer.
Add organization_id to every relevant table. Enable RLS. Write the policies. This is a two-day task at the start of a project. It is a two-week task with a migration risk after you have real customer data.
Decision 2: Auth That Survives Scale
Do not build your own auth. This is not a philosophical position. It is a practical one.
Password resets, email verification, MFA, SSO via SAML and OIDC, session management, token rotation, brute-force protection, leaked credential detection: these are all problems that a proper auth provider has already solved. Building them yourself introduces bugs in security-critical code and takes time away from your actual product.
Use Clerk or Auth0 from day one. Clerk is the better choice for most modern stacks: clean Next.js integration, organization management built in, and a developer experience that does not require a week of documentation reading. Auth0 is more mature for enterprise SSO requirements.
The cost is marginal at early scale. The alternative is spending 40 to 80 hours building auth infrastructure that is not your competitive advantage, then spending another 20 hours hardening it when a security researcher finds a gap.
One more thing: when your first enterprise customer asks for SAML SSO, you will have it ready. Founders who built custom auth spend $15,000 to $30,000 adding SSO as a custom integration later.
Decision 3: Payments and Billing on Day One
Wire Stripe Checkout and webhooks in week one. Not week eight. Not after you validate the product. Week one.
Billing logic is not isolated. It touches your user model, your feature flags, your API rate limits, your email flows, and your admin dashboard. When you add billing to an existing app, you are not adding a payments page. You are threading a concept through your entire codebase that was never designed to accommodate it.
Here is what retrofitting billing actually looks like: you need to add a subscription table and link it to your users table, update every feature gate to check the subscription status, handle the webhook events for payment failures and plan changes, update your onboarding flow, add billing pages to the settings UI, and fix every test that now breaks because users need a subscription to do anything.
Founders who skip billing at the start consistently report spending $10,000 to $30,000 in developer time to add it properly later, plus additional time debugging the edge cases introduced during the migration.
The setup is straightforward: Stripe Checkout handles the payment UI. Webhooks update your database when subscriptions are created, upgraded, or cancelled. A subscription_status column on your organization or user record gates your features. Three days of work in week one versus three weeks of work in month six.
Decision 4: A Background Job Queue Early
The moment your app needs to do anything that takes more than two seconds, you need async jobs. That moment arrives earlier than you expect.
Sending welcome emails, processing uploaded files, enriching leads with third-party APIs, generating AI-powered reports, sending weekly digests: none of these should block an HTTP request. If they do, you are lying to your users about request completion, and your server is one slow external API call away from a timeout cascade.
The right setup: BullMQ on Redis for teams that want control and visibility, or Trigger.dev for a fully managed solution with a clean UI for monitoring job runs. Both integrate cleanly with Node.js and TypeScript.
The pattern is simple. The user triggers an action. Your API creates a job, stores its ID, and returns a 202 Accepted response immediately. The job runs in the background. When it completes, you notify the user via a webhook, a UI update, or an email.
Without this infrastructure, you will eventually build it anyway, under pressure, after a production incident where a background task timed out and left data in an inconsistent state.
Decision 5: Observability From the Start
At 3am, when a paying customer cannot access their data and they are emailing you, you have two scenarios. In the first, you have structured logs, error tracking, and an uptime monitor. You find the problem in four minutes. In the second, you have console.log statements and no alerting. You spend two hours in the dark.
The minimum viable observability stack:
- Sentry: Catches and groups unhandled exceptions with full stack traces and request context. Free tier covers most early-stage products. Set it up in 20 minutes and never debug a blank error screen again.
- Structured logging: Log events as JSON objects with consistent fields:
user_id,organization_id,action,duration_ms,status. This makes logs searchable and parseable. Use Pino or Winston in Node.js. Ship logs to Logtail or Axiom. - Uptime monitoring: Betterstack or UptimeRobot. Get a text message when your app goes down. This is a five-minute setup.
Observability is not about being fancy. It is about not flying blind when something breaks in production. Every serious engineering team treats this as table stakes. You should too.
The Real Cost of Getting This Wrong
Each of these decisions takes one to three days to implement correctly in week one of your build. Here is what they cost when you skip them:
| Decision | Cost to retrofit |
|---|---|
| Multi-tenancy | 2 to 4 weeks, high migration risk |
| Auth | 3 to 6 weeks, security exposure |
| Billing | 2 to 5 weeks, $10k to $30k dev time |
| Job queue | 1 to 2 weeks, production incidents |
| Observability | Ongoing: every bug takes 5x longer to diagnose |
The irony is that none of these are hard to get right at the start. They are only expensive when you do them late.
We make all five of these decisions in week one on every build. That is why our clients launch in three to five weeks and do not have infrastructure fires six months later. If you are about to start building, talk to us first at spofylabs.com. One conversation can save you tens of thousands in rework.