Skip to main content

When to Outgrow Your SaaS Boilerplate in 2026

·StarterPick Team
Share:

Every Boilerplate Has an Expiration Date

A SaaS boilerplate is a starting point, not a final destination. It's designed to get you from zero to first customer quickly. But as your product grows, the assumptions baked into that boilerplate start to constrain you.

This is not a failure of the boilerplate — it's a sign your product has succeeded. The question is: when do you stop working within the boilerplate and start working around it?


Signs You've Outgrown Your Boilerplate

1. You're fighting the auth system

// Early stage: boilerplate auth is sufficient
const session = await auth();
const user = session?.user;

// Growth stage: you need things the boilerplate doesn't support
// - Custom JWT claims for RBAC
// - Organization-level permissions
// - SSO / SAML for enterprise customers
// - Device fingerprinting for security
// - Custom session storage

// Symptom: You've patched auth.ts so many times it's
// unrecognizable from the original

What to do: If Auth.js doesn't support what you need, migrate to Clerk (managed) or Better Auth (flexible). Don't hack auth — it's the most security-critical part of your stack.

2. Your database schema is radically different from the original

// Original boilerplate schema:
model User {
  id        String @id
  email     String @unique
  plan      String
}

// Your schema after 18 months:
model User {
  id                    String   @id
  email                 String   @unique
  // 40+ additional fields
  organizationId        String?
  role                  UserRole
  customDomain          String?
  apiRateLimit          Int
  stripeCustomerId      String?
  stripeSubscriptionId  String?
  svixAppId             String?
  usageTokensThisMonth  Int
  // ... etc
}

When your schema differs this significantly from the original, the boilerplate's auth helpers, billing hooks, and admin queries no longer match your data model. You're maintaining parallel systems.

What to do: This is healthy growth — it means you've built a real product. Document the divergence. Stop trying to reconcile with upstream updates.

3. You need infrastructure the boilerplate doesn't have

Boilerplate provides: Serverless functions on Vercel

You now need:
  - Background workers (email processing, data pipelines)
  - WebSocket servers (real-time collaboration)
  - Message queues (event-driven processing)
  - Dedicated compute (ML model inference)
  - Multi-region deployment

Symptom: You're deploying services outside the boilerplate
framework and the app isn't coherent anymore

What to do: Add services as needed (Railway worker service, Inngest for jobs). The boilerplate becomes the web layer, not the entire app.

4. Your performance requirements exceed the default architecture

// Boilerplate default: every request hits the database
export default async function ProductList() {
  const products = await db.product.findMany();
  return <Products data={products} />;
}

// At scale (100K+ users, 10K+ products):
// You need: Redis caching, CDN, read replicas, cursor-based pagination
// None of which are in the boilerplate

// Symptom: Database CPU at 90%, page load >3s, Vercel function timeouts

What to do: Add caching (Upstash Redis), optimize queries, add indexes. These are architectural additions, not replacements of the boilerplate.

5. The boilerplate is actively blocking features you need

// Boilerplate assumes single-tenant users
// You need multi-tenant organizations
// → Had to rewrite auth context, billing logic, API design

// Boilerplate uses Prisma with PostgreSQL
// You need sharding for a specific data type
// → Fighting the ORM to support your access patterns

// Boilerplate uses a specific email provider
// Your enterprise customers require SES + custom SMTP
// → Abstraction layer doesn't support it

When the boilerplate assumptions actively prevent features customers are requesting, you've crossed the line from "working with" to "working around."


What You Should Never Rewrite

Before you consider a full rewrite, be honest about what the boilerplate actually provides that you'd have to rebuild:

Stripe webhook handling:

// This looks simple but has 6 edge cases:
// 1. Signature verification
// 2. Idempotent event processing
// 3. Subscription state machine (active → past_due → canceled)
// 4. Dunning flow (retry failed payments)
// 5. Plan change proration handling
// 6. Trial period management

// Time to rebuild correctly: 2-4 weeks
// Time to break: 30 minutes of reckless editing

Auth session management: The boilerplate handles CSRF protection, secure cookie settings, session rotation, and token refresh. These are easy to get wrong and hard to debug.

Email delivery: Bounce handling, unsubscribe compliance, transactional vs marketing separation. The boilerplate's email integration gets this right by default.


The Migration Spectrum

You don't have to choose between "keep the boilerplate as-is" and "rewrite from scratch." There's a spectrum:

Level 1: Extend with new services

When: Need background jobs, WebSockets, or storage that Vercel doesn't provide. Action: Deploy additional services (Railway worker, S3, Upstash) alongside the boilerplate app. Disruption: Minimal. The boilerplate web layer stays intact.

Level 2: Replace individual subsystems

When: Auth system doesn't meet requirements, billing logic is too different. Action: Replace auth module with Clerk or Better Auth. Replace billing hooks with custom implementation. Disruption: Moderate. Other parts of the boilerplate still work.

Level 3: Extract to a service

When: A specific domain (notifications, file processing, AI pipeline) needs its own deployment. Action: Extract to a separate service with its own database tables and API. Disruption: Moderate. Main app calls the service via API.

Level 4: Full architectural migration

When: The entire data model, deployment model, or tech stack needs to change. Action: Build the new architecture in parallel, migrate users gradually. Disruption: High. Essentially a rebuild.

Level 4 is almost always the wrong choice. Products that do level 4 typically underestimate the work by 5-10x.


The Refactoring Signals

How do you know which level you need?

"We can't ship feature X because of the boilerplate" → Level 2-3
  Is it auth? → Level 2 (replace auth module)
  Is it infrastructure? → Level 1-3 (add service)
  Is it the entire data model? → Level 3-4 (extract domain)

"The boilerplate code is slowing us down" → Usually Level 1-2
  Specific files are messy → Refactor those files
  Entire architecture is wrong → Level 3-4 (rare and expensive)

"Enterprise customers need X" → Level 2-3
  SSO/SAML → Replace auth (Level 2)
  Custom data isolation → Extract tenant service (Level 3)
  On-premise deployment → Architecture migration (Level 4)

When to Start Fresh

Starting fresh is justified when:

  1. Your tech stack is fundamentally wrong for your scale. You need PHP/Elixir/Go for performance and the JavaScript stack is the bottleneck — not just slow code.

  2. The boilerplate has security issues you can't patch. Not theoretical — actual CVEs that require architectural changes.

  3. A major acquisition or pivot requires a different codebase. Business reasons override technical continuity.

  4. The team has grown beyond the original architecture's capacity. 20+ engineers working in a monolith designed for 1-2 developers causes organizational friction that's as real as technical debt.

Note: "We don't like the boilerplate's style" is not a valid reason. Refactor incrementally.


The Right Boilerplate Transition

If you do decide to adopt a different boilerplate or rebuild:

  1. Never big-bang migrate. Run both systems in parallel.
  2. Migrate the simplest components first. Don't start with auth or billing.
  3. Keep the database as the source of truth. Both systems can read the same tables during transition.
  4. Set a deadline. Open-ended parallel systems are technical debt that lasts years.

Conclusion

Most SaaS products don't outgrow their boilerplate — they outgrow specific assumptions the boilerplate made. The answer is almost always surgical replacement of those assumptions, not a full rewrite.

Respect what the boilerplate got right. Build on top of what works. Replace only what genuinely constrains you.

StarterPick helps you choose a boilerplate with the right architectural assumptions for your product's eventual scale — so you outgrow it later rather than sooner.

Signals from Your Codebase: When the Numbers Tell the Story

Before making architectural decisions about replacing subsystems, look at the empirical signals your codebase is sending.

A useful measure: the ratio of boilerplate code to product code in files you edit regularly. Early on, you're editing boilerplate files frequently as you customize them. At maturity, you should be spending 90%+ of your development time in files that are purely your product — not auth.ts, not stripe/webhook, not lib/email.ts. If you're still regularly editing boilerplate-originated files 18 months after launch, one of two things is true: either you're still heavily customizing the boilerplate (normal for complex products), or the boilerplate's abstractions are not serving you (it's blocking what you need to build).

A second signal: the TODO: fix boilerplate issue comment count. Engineers leave these when they work around a limitation and plan to return. A codebase with 50+ such comments from boilerplate-related issues is carrying significant architectural debt. Walk through them periodically and assess: are they workarounds for fundamental limitations, or known minor issues you keep deferring? The former requires architectural change; the latter is normal technical debt.

The third signal is onboarding friction for new engineers. When a new engineer joins and asks "why does auth work this way?", the answer should be "here's the Auth.js docs." When the answer is "we have some custom patches, let me explain the history," the boilerplate has become a custom framework. This is fine — it happens to every product — but it means your onboarding cost is now the entire custom framework, not the standard boilerplate.

The Feature Blocklist: Patterns That Cannot Fit

Some features genuinely cannot be built within a boilerplate's constraints without effectively replacing the boilerplate. Recognizing these early saves months of attempted workarounds.

Real-time multiplayer or collaborative features (think Google Docs-style simultaneous editing) require WebSocket servers or SSE streams that persist while users are active. Vercel's serverless model cannot support persistent WebSocket servers. If your product needs real-time collaboration, you need Railway, Fly.io, or a self-hosted setup — or you offload real-time to a service like PartyKit or Liveblocks. The boilerplate's Vercel deployment assumption must change.

Hybrid search (vector + full-text) over large document collections becomes slow as the document count grows if you're using a shared-row PostgreSQL table. At 1M+ document chunks, the semantic search query may take 2–5 seconds even with indexes. This isn't a boilerplate limitation specifically — it's a scale problem that requires either a dedicated vector database (Pinecone, Qdrant), pgvector with more aggressive HNSW index tuning, or a different chunking strategy. Recognizing this before the documents table hits 500K rows gives you time to architect the solution.

White-labeling requirements — where enterprise customers need your product running under their own domain with their own branding — often exceed what standard boilerplates provide. Makerkit and Supastarter support multiple tenants with custom branding, but full white-labeling (separate Next.js app per customer, separate DNS, separate SSL) requires infrastructure automation that no standard boilerplate handles. When the first enterprise customer asks for white-labeling, budget 2–4 weeks of infrastructure work regardless of your starting boilerplate.

Planning the Extraction: A Template for Subsystem Replacement

When you've confirmed that a subsystem needs replacement (auth, billing, or a major infrastructure component), the extraction pattern reduces risk significantly.

The strangler fig pattern applies to boilerplate subsystem replacement: build the new system alongside the old one, migrate incrementally, and remove the old system only when the new one handles 100% of the load. Never do a big-bang replacement of auth or billing — the risk of data loss or service interruption is too high.

For auth replacement (say, NextAuth → Better Auth): first, install Better Auth alongside NextAuth without activating it. Write the new auth configuration and test it with a test user. Migrate a small percentage of new sign-ups to Better Auth while existing users continue using NextAuth sessions. Once new sign-ups are stable, migrate existing sessions using a background job that reads the old session format and creates a new one. Cut over the middleware to validate Better Auth sessions when the user count on Better Auth exceeds 80%. Decommission NextAuth only after all sessions have migrated or expired.

The same pattern for billing: add the new payment provider integration alongside Stripe. Route new sign-ups to the new provider. Give existing customers a migration path (an in-app prompt: "upgrade your payment method for continued access"). Remove the old integration after 90% migration. This avoids breaking existing subscribers while allowing new customers to use the improved system.


Read how to migrate between SaaS boilerplates for a full treatment of the migration process.

See the boilerplate trap and technical debt guide for how to recognize when the accumulation point is approaching.

Start with the right architectural foundation: best SaaS boilerplates 2026 rated by scalability and architectural extensibility.

The SaaS Boilerplate Matrix (Free PDF)

20+ SaaS starters compared: pricing, tech stack, auth, payments, and what you actually ship with. Updated monthly. Used by 150+ founders.

Join 150+ SaaS founders. Unsubscribe in one click.