Skip to main content

Wasp vs T3 Stack vs ShipFast: Full-Stack Framework vs Boilerplate 2026

·StarterPick Team
waspt3-stackshipfastsaas-boilerplatefull-stack2026

TL;DR

These aren't comparable — they solve different problems at different price points. Wasp is a full-stack framework with a DSL that generates auth, backend, and deployment config — you write less but accept the framework's model. T3 Stack is a free, widely-understood collection of best-practice tools (Next.js + tRPC + Prisma) that you own completely. ShipFast is a $299 paid template for fast B2C SaaS launch. The right choice depends on what you're optimizing: control (T3), speed with generated code (Wasp), or speed with polished components (ShipFast).

Key Takeaways

  • Wasp: free, DSL framework (.wasp files), generates React + Node.js, built-in auth/email/jobs/deploy
  • T3 Stack: free, Next.js + tRPC + Prisma + NextAuth + Tailwind, create-t3-app CLI
  • ShipFast: $299 paid template, Next.js + Drizzle + Stripe, battle-tested landing pages
  • Control: T3 > ShipFast > Wasp (Wasp abstracts away Node.js layer)
  • Time to first feature: Wasp > ShipFast > T3
  • Scalability ceiling: T3 > ShipFast > Wasp (Wasp is newer, less proven at scale)
  • Community: T3 (largest) > ShipFast > Wasp

Wasp: The Framework Approach

Wasp takes a fundamentally different position: it's a full-stack framework, not a template. You describe your app in a .wasp DSL file, and Wasp generates the entire React + Express.js + database wiring.

// main.wasp — the entire app definition:
app TodoApp {
  wasp: { version: "^0.14.0" },
  title: "My SaaS App",

  auth: {
    userEntity: User,
    methods: {
      email: {
        fromField: { name: "My SaaS", email: "hello@mysaas.com" },
        emailVerification: { clientRoute: VerifyEmailRoute },
        passwordReset: { clientRoute: PasswordResetRoute },
      },
      google: {},
      github: {},
    },
    onAuthFailedRedirectTo: "/login",
    onAuthSucceededRedirectTo: "/dashboard",
  },

  emailSender: {
    provider: Mailgun,
  },
}

// Database entity:
entity User {=psl
  id          Int       @id @default(autoincrement())
  email       String    @unique
  createdAt   DateTime  @default(now())
  isAdmin     Boolean   @default(false)
  subscription Subscription?
psl=}

entity Subscription {=psl
  id          Int       @id @default(autoincrement())
  userId      Int       @unique
  user        User      @relation(fields: [userId], references: [id])
  plan        String    @default("free")
  stripeId    String?
psl=}

// Routes and pages:
route DashboardRoute { path: "/dashboard", to: DashboardPage }
page DashboardPage {
  component: import { DashboardPage } from "@client/pages/Dashboard",
  authRequired: true,
}

// Server action:
action updateSubscription {
  fn: import { updateSubscription } from "@server/actions/subscription",
  entities: [User, Subscription],
}

// Background job:
job sendWeeklyDigest {
  executor: PgBoss,
  perform: { fn: import { sendWeeklyDigest } from "@server/jobs/digest" },
  schedule: { cron: "0 9 * * 1" },  // Every Monday 9am
}
// Wasp server action (plain TypeScript):
import { type UpdateSubscription } from 'wasp/server/operations';
import { type Subscription } from 'wasp/entities';

export const updateSubscription: UpdateSubscription<
  { plan: string },
  Subscription
> = async ({ plan }, context) => {
  if (!context.user) throw new Error('Not authenticated');
  
  return context.entities.Subscription.upsert({
    where: { userId: context.user.id },
    update: { plan },
    create: { userId: context.user.id, plan },
  });
};
// Wasp client (automatically typed):
import { useQuery, useAction } from 'wasp/client/operations';
import { getSubscription, updateSubscription } from 'wasp/client/operations';

export function DashboardPage() {
  const { data: subscription } = useQuery(getSubscription);
  const upgrade = useAction(updateSubscription);
  
  return (
    <div>
      <p>Current plan: {subscription?.plan}</p>
      <button onClick={() => upgrade({ plan: 'pro' })}>Upgrade</button>
    </div>
  );
}

T3 Stack: The Community Standard

npm create t3-app@latest my-app
# Prompts: TypeScript? Yes | tRPC? Yes | Prisma? Yes | NextAuth? Yes | Tailwind? Yes
// T3 tRPC router — full type safety end-to-end:
import { z } from 'zod';
import { createTRPCRouter, protectedProcedure } from '~/server/api/trpc';

export const subscriptionRouter = createTRPCRouter({
  get: protectedProcedure.query(async ({ ctx }) => {
    return ctx.db.subscription.findFirst({
      where: { userId: ctx.session.user.id },
    });
  }),
  
  update: protectedProcedure
    .input(z.object({ plan: z.enum(['free', 'pro', 'team']) }))
    .mutation(async ({ ctx, input }) => {
      return ctx.db.subscription.upsert({
        where: { userId: ctx.session.user.id },
        update: { plan: input.plan },
        create: { userId: ctx.session.user.id, plan: input.plan },
      });
    }),
});
// T3 client usage:
import { api } from '~/trpc/react';

export function DashboardPage() {
  const { data: subscription } = api.subscription.get.useQuery();
  const upgrade = api.subscription.update.useMutation();
  
  return (
    <div>
      <p>Current plan: {subscription?.plan}</p>
      <button onClick={() => upgrade.mutate({ plan: 'pro' })}>Upgrade</button>
    </div>
  );
}

T3 doesn't include: landing pages, Stripe integration, email templates, admin dashboard. You build these yourself.


ShipFast: The Paid Template

ShipFast takes the "copy-paste into your project" approach rather than framework abstraction. Marc Lou's own product.

// ShipFast: Everything is just Next.js — you see all the code.
// The value is in the pre-built components and patterns.

// Stripe webhook handler (pre-built in ShipFast):
import Stripe from 'stripe';
import { db } from '@/lib/db';
import { users } from '@/lib/db/schema';
import { eq } from 'drizzle-orm';

export async function POST(req: Request) {
  const body = await req.text();
  const sig = req.headers.get('stripe-signature')!;
  
  const event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
  
  if (event.type === 'checkout.session.completed') {
    const session = event.data.object as Stripe.Checkout.Session;
    await db.update(users)
      .set({
        stripeCustomerId: session.customer as string,
        stripeSubscriptionId: session.subscription as string,
        stripePriceId: session.metadata?.priceId,
        stripeCurrentPeriodEnd: new Date(session.expires_at * 1000),
      })
      .where(eq(users.email, session.customer_email!));
  }
  
  return new Response(null, { status: 200 });
}

ShipFast includes: Landing page sections (Hero, Pricing, FAQ, Testimonials), Stripe + LemonSqueezy, NextAuth, blog, SEO setup, deployment config. No DIY.


Comparison Table

WaspT3 StackShipFast
PriceFreeFree$299
TypeFrameworkTemplate/StackPaid template
API layerGeneratedtRPCREST/API routes
AuthBuilt-in (DSL)NextAuthNextAuth/Supabase
PaymentsManualManualStripe + LemonSqueezy
Landing pagesManualManual✅ Built-in
AdminManualManual
Background jobs✅ PgBoss DSLManualManual
Node.js controlLimitedFullFull
Learning curveHigh (new DSL)MediumLow
CommunityGrowingLargeLarge

Decision Guide

Choose Wasp if:
  → Want framework-level abstractions (less code to write)
  → Background jobs and cron are core to your app
  → Comfortable learning Wasp's DSL
  → Free is a hard requirement
  → Like opinionated scaffolding

Choose T3 Stack if:
  → Want full control with zero license cost
  → Team knows React/TypeScript well
  → Building a complex product with custom requirements
  → Want the best type-safety (tRPC end-to-end)
  → Fine with building Stripe/auth flows yourself

Choose ShipFast if:
  → Launching a B2C SaaS solo
  → Want landing pages, payments, auth ready-to-go
  → $299 investment justified by saved dev time
  → Targeted at Marc Lou's audience of indie hackers
  → Want LemonSqueezy as payment option

Compare Wasp, T3 Stack, and ShipFast in detail at StarterPick.

Comments