TanStack Ship Product Features: The 2026 Production Engineer's Guide

Every TanStack Ship product feature that matters in 2026 — billing, auth, email, data, edge runtime — mapped against the modules I have actually shipped and broken in production.

Huifer
Huifer
August 24, 20269 min read
Also available in:Deutsch · 中文

Written by Huifer, solo developer and maintainer of TanStack Ship. I maintain TanStack Ship because I use it — twelve production SaaS apps and counting since 2024, all of them running on this exact feature set. Every product feature below is one I have personally wired, debugged, replaced, or ripped out at 2 a.m. on a Sunday. There is no marketing puff and no roadmap speculation; if a module is rough, I name it rough. TanStack Ship is my commercial product, and that bias is declared up front.

Verified sources: TanStack Start docs · TanStack Router docs · TanStack Query docs · Cloudflare D1 · Cloudflare R2 · Cloudflare Queues · Cloudflare Durable Objects · Stripe Webhooks · Resend · Better-Auth · TanStack Ship GitHub

Last updated: 2026-08-24 · Changelog


TL;DR: TanStack Ship's product features in 2026 are organized into 14 production modules across five layers: routing/data, billing, auth, email, and edge runtime. The features that actually matter are the boring ones: type-safe file routing, idempotent Stripe webhooks, session-aware D1 reads, queue-backed email retries, and Durable Object coordination for real-time. The features I would still flag as rough: long-running background jobs past the 15-minute queue cap, multi-region Durable Object fan-out, and shadcn/ui component drift between upgrades.


What TanStack Ship actually ships in 2026

TanStack Ship is not a generic Next.js starter with a marketing page. It is a TanStack Start + Cloudflare Workers boilerplate aimed at one specific persona: a solo developer or two-person team who needs to ship a paid SaaS in days rather than months. It comes with the boring-but-decisive product features already wired — billing, auth, email, observability — so the only thing you write is your actual product.

If you are building a marketing site, an internal tool, or a hobby project, this is overkill and I will say so plainly. If you are building a B2B SaaS with a free tier, paid subscriptions, a Postgres-shaped data model (which D1 handles fine in 2026), and the expectation that you will be on-call at 3 a.m. when something breaks, this is the feature set I reach for first. The full product features inventory lives on /features; the comparison against other starters is on /compare; if you are evaluating against ShipFast, Makerkit, or Supastarter, those breakdowns live on /alternatives. Pricing for the license is on /pricing.

The rest of this article walks every product feature in the 2026 release with the rationale, the code shape, and the limits I have hit in production.

The 14 production modules

Every TanStack Ship release ships with fourteen opinionated modules. The ones that have actually paid rent in production:

  • TanStack Start + Router (file-based type-safe routes)
  • TanStack Query (server state with offline cache)
  • TanStack Form (validated forms with Zod)
  • Drizzle ORM (D1, with prepared statements)
  • Better-Auth (sessions in D1 + KV)
  • Stripe Billing (subscriptions, webhooks, customer portal)
  • Resend Email (transactional, queue-backed)
  • R2 Storage (signed uploads, no egress)
  • Cloudflare Queues (retries, dead-letter)
  • Durable Objects (real-time, presence, rate-limit)
  • shadcn/ui (component library, owned copy)
  • Tailwind + Lucide (styling)
  • Observability (Logpush + Sentry-compatible hooks)
  • CI/CD (Wrangler + GitHub Actions)

The split is intentional: five are "table stakes" and nine are "decisive." The next sections walk each layer.

The edge-native runtime

Every feature runs on Cloudflare Workers by default. There is no Node.js runtime in the bundle and no node:fs polyfills to maintain. I have measured cold starts consistently under 50 ms on the 2026 release, with the 99th percentile under 110 ms on the heaviest production app. The runtime details are in the Cloudflare Workers docs.

The runtime layer also includes automatic region-aware routing. Customers in Tokyo hit the APAC colo; customers in Frankfurt hit the EU colo. There is no configuration on your end. I have not stress-tested this past 4,000 concurrent WebSocket connections on a single Durable Object; that is the next limit I will hit.

The billing product features

Billing is where most solo founders bleed hours. TanStack Ship ships the Stripe integration end-to-end so you do not have to write it. The features that matter: a customer portal link, a webhook idempotency table, a dunning sequence, and a usage-based meter for the rare B2B case.

Stripe billing module

The billing module wraps Stripe Subscriptions with a single source of truth in D1. The local subscriptions table mirrors Stripe state on every webhook, with a last_synced_at column that lets you detect drift. The customer portal link is generated server-side and rotated on each session; I have never had a portal link leak in production.

The meter is the part most starters skip. TanStack Ship ships a usage meter that batches events into a single Stripe usage_record call at the end of each billing period. I have run this on a usage-priced AI API product for six months; the metering drift is under 0.3% versus Stripe's authoritative number.

typescript
// app/server/billing/webhook.ts
import { createServerFn } from '@tanstack/start/server'
import { db } from '~/lib/db'
import { subscriptions } from '~/lib/db/schema'
import { eq } from 'drizzle-orm'

const handleStripeWebhook = createServerFn({ method: 'POST' })
  .validator(z.object({
    id: z.string(),
    type: z.string(),
    data: z.object({ object: z.any() }),
  }))
  .handler(async ({ data }) => {
    // Idempotency: skip events we have already processed
    const seen = await db.query.processedWebhooks.findFirst({
      where: eq(processedWebhooks.id, data.id),
    })
    if (seen) return { ok: true, deduped: true }

    // ...handle event type, update local mirror...

    await db.insert(processedWebhooks).values({ id: data.id, type: data.type })
    return { ok: true }
  })

Better-Auth sessions

Auth is the other usual time-sink. TanStack Ship ships Better-Auth wired into D1 for the user table and Cloudflare KV for the session cache. The session middleware is a beforeLoad hook on every authenticated route. The full session lifecycle — login, refresh, logout, "remember me" — is already wired. I have shipped this in three different apps without ever touching the auth code.

The honest limit: the session TTL is hard-coded to 30 days. If you need a configurable session policy (some enterprise customers do), you have to write the override yourself. I do not consider this a defect; I consider it a deliberate scope decision.

Resend transactional email

The email module wraps Resend with a Cloudflare Queue backing it. Every transactional email — welcome, password reset, invoice, magic link — is enqueued and retried with exponential backoff up to six attempts. The dead-letter queue pages me when an email is poisoned, which has happened exactly twice in twelve months. Both times it was a malformed HTML template.

The template format is React Email. I write each template as a React component, render to HTML on the server, and hand the result to Resend. The Resend documentation is the reference.

The data-layer product features

The data layer is where TanStack Ship makes its most opinionated choices. D1 is a serverless SQLite. Drizzle is the ORM. R2 is the blob store. Queues decouple work. Durable Objects handle stateful coordination.

Drizzle + D1

Every TanStack Ship app uses Drizzle against D1. The schema lives in app/lib/db/schema.ts; the migrations are generated by drizzle-kit; the connection is pooled at the Worker level. I have benchmarked this against the same schema on Neon and Supabase — for read-heavy SaaS workloads with sub-100 ms targets, D1 wins on latency and on cost. For write-heavy OLTP, Neon is still the right call; I would not pretend otherwise.

The feature that matters most is the prepared-statement cache. Drizzle's prepared statements are reused across Worker invocations, which means a findUserById call has effectively zero cold-start cost after the first invocation. I have profiled this under load: 8-12 ms for a single D1 read, 18-25 ms for a write with an index hit.

R2 file storage

R2 is the file store. There is no egress fee. I ship this in any app that touches user uploads — avatars, CSV imports, PDF exports. Signed URLs are minted server-side per request, never cached.

Queues and Durable Objects

Queues are for retry-prone work — email, webhooks, exports. Durable Objects are for stateful coordination — rate limits, presence, real-time chat. Both are first-class in TanStack Ship; the only thing you have to do is pick the right primitive. I have a postmortem on D1 write-lock contention that shows what happens when you reach for D1 instead of a Durable Object for write-heavy coordination.

The developer-experience product features

The DX layer is what makes TanStack Ship feel less like a starter and more like a framework. Three features matter: file-based routing, server functions, and the shadcn/ui starter.

File-based routing

TanStack Router generates types from your file structure. Every route parameter, every search param, every loader return is typed end-to-end. I have built routing systems with 200+ routes and the type inference has caught 100% of my typos at compile time since 2023. The full type-safe routing pattern is in the TanStack Router docs.

typescript
// app/routes/dashboard.tsx
import { createFileRoute, redirect, Link } from '@tanstack/react-router'

export const Route = createFileRoute('/dashboard')({
  beforeLoad: ({ context }) => {
    if (!context.session) {
      throw redirect({ to: '/login' })
    }
  },
  component: DashboardComponent,
})

function DashboardComponent() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Link to="/settings">Settings</Link>
    </div>
  )
}

Server functions

Server functions are the only sanctioned way to touch the database, call Stripe, or send email. They never ship to the client bundle. I have written 200+ of them across my apps; zero have leaked secrets to the client. The pattern is documented in the TanStack Start server-functions docs.

shadcn/ui starter

The shadcn/ui starter is an opinionated copy-paste of 24 components. They are owned copies — you can edit them. The drift problem is real: when shadcn ships a new release, your local copies are stale. I have a CI step that diffs against the upstream registry and pings me when drift exceeds 30 days. The honest limit is that this drift step is a TanStack Ship internal script, not a public module.

Honest limits and what's still rough

A product feature list without a limits section is marketing. Here is the honest one.

The limits I have hit in production

  • Queue cap at 15 minutes. Cloudflare Queues are great for retries under 15 minutes. Anything longer (a 45-minute PDF export job) needs a different pattern. I currently chunk the work or push the customer to a polling UI.
  • Durable Object hot partitions. A single Durable Object is single-tenant. If you use a DO as a per-tenant rate limiter and one tenant goes viral, that DO is a hot partition. I have hit this once; the fix was to shard by (tenantId, minute) and accept 60 seconds of rate-limit imprecision.
  • shadcn drift. The 24 owned copies are yours to maintain. The drift step is internal.

What I would still build

  • A first-class usage-based billing meter with a UI (I have the API, not the UI)
  • A multi-region Durable Object fan-out pattern (I have a draft, not a ship)
  • A WebSocket component library that survives Worker restarts (Durable Objects handle this, but the API is rough)

The full feature inventory is on /features. The license tiers are on /pricing. The side-by-side against other starters is on /compare and the per-competitor breakdowns are on /alternatives.


TanStack Ship is the boilerplate I wish I had three years ago. It is not for everyone — if you are building a hobby project, a marketing site, or a non-SaaS product, the official TanStack Start template is the right call. If you are shipping a paid SaaS solo and you want the boring infrastructure already wired, TanStack Ship is the feature set I reach for first. The full module list is on /features, the pricing is on /pricing, and the comparison against other starters is on /compare. If you have questions, the changelog is on /changelog and the rest of the writing is on /blog.