TanStack Start Production-Ready SaaS: What You Actually Need Before You Launch

By Aziz Ali
tanstack-startsaasproductiondeploymenttypescript

Most tutorials show you how to run bun dev and call it a day. Getting TanStack Start to actually production-ready — handling real users, real payments, real auth sessions, and real errors — is a different game entirely. I've shipped two SaaS products on TanStack Start and learned exactly where the sharp edges are.

Here's my honest checklist.

TanStack Start IS Production-Ready — With Caveats

Let me be direct: TanStack Start is absolutely production-ready in 2026. The framework's server functions, type-safe routing, and seamless React Query integration make it one of the best choices for full-stack TypeScript SaaS. But "production-ready" doesn't mean "zero configuration after bun create."

You still need to wire up:

  • Auth with real session persistence
  • Payment webhooks that don't silently fail
  • Error tracking so you know when things break
  • Database migrations you can run without downtime
  • A deployment target that won't bankrupt you

Let me walk through each one.

Auth: Sessions Need to Survive Restarts

The most common gotcha I see: auth sessions that vanish every time you redeploy. If you're using Better-Auth (which is what I recommend — see the setup guide here), make sure your session store is backed by your database, not in-memory.

Here's the config that actually works in production with Drizzle:

// src/lib/auth.ts
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "./db";

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: "pg", // or "sqlite" for local dev
  }),
  session: {
    expiresIn: 60 * 60 * 24 * 30, // 30 days
    updateAge: 60 * 60 * 24,       // refresh daily
    cookieCache: {
      enabled: true,
      maxAge: 5 * 60, // 5-minute client cache
    },
  },
  emailAndPassword: {
    enabled: true,
  },
});

That cookieCache setting is the difference between "auth works" and "auth works without hammering your DB on every request." In production with real traffic, this matters.

Payments: Stripe Webhooks Are Your Source of Truth

This is where I've seen more production incidents than anywhere else. Your app processes a checkout — great. But what happens when Stripe tries to tell you about a failed renewal 48 hours later and your webhook endpoint returns a 500?

You need idempotent webhook handling. Full stop. I covered this in depth in Stripe Webhooks in TanStack Start, but the short version:

// src/routes/api/webhooks/stripe.ts
import Stripe from "stripe";
import { db } from "~/lib/db";
import { processedWebhooks } from "~/lib/schema";

export const POST = async ({ request }: { request: Request }) => {
  const sig = request.headers.get("stripe-signature")!;
  const body = await request.text();

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
  } catch {
    return new Response("Invalid signature", { status: 400 });
  }

  // Idempotency check — never process the same event twice
  const existing = await db.query.processedWebhooks.findFirst({
    where: (t, { eq }) => eq(t.stripeEventId, event.id),
  });
  if (existing) return new Response("Already processed", { status: 200 });

  // Handle and record
  await handleStripeEvent(event);
  await db.insert(processedWebhooks).values({ stripeEventId: event.id });

  return new Response("OK", { status: 200 });
};

Store processed event IDs. It's 5 minutes of work that prevents charging customers twice.

Database: Migrations Without Downtime

Drizzle's migration story is clean, but you need a production workflow. Here's what I run before every deploy:

# Generate the migration
bunx drizzle-kit generate

# Review it — always review migrations before running
cat drizzle/migrations/0012_*.sql

# Apply in production
bunx drizzle-kit migrate

The critical rule: never run drizzle-kit push in production. That's for development only. push skips migration files entirely and directly syncs your schema — you will lose data.

For zero-downtime migrations on Postgres, make additive changes only: add columns with defaults, never drop or rename columns in the same deploy that removes the old code.

Deployment: Pick Your Target Early

I've tried a few options. For TanStack Start in 2026, here's my honest ranking:

flowchart TD
    A[Your TanStack Start App] --> B{Traffic expectations?}
    B -->|Low to medium| C[Cloudflare Workers]
    B -->|Heavy compute or DB| D[Fly.io / Railway]
    B -->|Want zero ops| E[Vercel — but watch the bill]
    C --> F[Fast, cheap, global edge]
    D --> G[Persistent connections, great for Bun]
    E --> H[Convenient, expensive at scale]

Cloudflare Workers with Bun is my default pick for new SaaS products — you get global edge distribution and you won't get a $300 invoice surprise. See the full deployment guide here.

For anything requiring persistent DB connections (Drizzle with a real Postgres instance), Railway or Fly.io are better fits.

Error Tracking: You Need to Know Before Users Email You

Don't ship without error tracking. The setup is 10 minutes but saves hours of debugging:

// src/entry-server.tsx — Sentry for TanStack Start
import * as Sentry from "@sentry/bun";

Sentry.init({
  dsn: process.env.SENTRY_DSN,
  environment: process.env.NODE_ENV,
  tracesSampleRate: process.env.NODE_ENV === "production" ? 0.1 : 1.0,
});

Sentry has a generous free tier. There's no reason not to have it.

The Production Readiness Checklist

Here's the full list I run through before every launch:

Item Why It Matters
DB-backed sessions Sessions survive redeploys
Webhook idempotency No duplicate charges
Drizzle migrations (not push) No accidental data loss
Environment variables validated Fail fast on missing config
Error tracking (Sentry) Know before users tell you
Stripe webhook secret verified Reject spoofed events
Rate limiting on auth routes Prevent brute force
Health check endpoint /api/health Deployment rollback detection

FAQ

Is TanStack Start stable enough for production in 2026? Yes. The core APIs are stable, it's being used in production by a growing number of teams, and the TanStack ecosystem (Query, Router, Form) is battle-tested. The biggest risk is ecosystem immaturity for niche integrations — but for a SaaS with standard auth, payments, and email, you're fine.

Can I use TanStack Start with a Postgres database in production? Absolutely. Pair it with Drizzle ORM and a managed Postgres on Neon, Railway, or Supabase. Drizzle's type-safe queries and migration tooling are excellent in production environments.

What's the best hosting for TanStack Start? Cloudflare Workers for edge-heavy apps, Fly.io or Railway if you need persistent server-side state. Avoid Vercel unless you enjoy surprise bills as you scale.

Do I need a separate API layer? No. TanStack Start's server functions and API routes handle everything you need. I've shipped full SaaS products with zero separate API services — all in one repo, all type-safe.

What about background jobs in TanStack Start? For simple tasks (email after signup, webhook processing), handle them inline in server functions. For heavier workloads, add a queue like BullMQ with your Bun runtime — it works well.


Going production-ready is less about TanStack Start specifically and more about doing the boring infrastructure work properly. The framework is solid. The rest is on you.

If you want to skip the auth plumbing, Stripe setup, and email wiring, BetterStarter ships with all of it pre-configured and production-tested — $99 one-time. You clone, configure your env vars, and ship. That's the point.

For more on the full stack, the TanStack Start tech stack breakdown is worth a read before you finalize your architecture choices.