Skip to main content

Remix SaaS vs Epic Stack 2026: Which Remix Boilerplate Should You Choose?

·StarterPick Team
remixepic-stackremix-saascomparison2026

TL;DR

Epic Stack prioritizes production practices; Remix SaaS prioritizes features. Epic Stack (by Kent C. Dodds) ships with SQLite, comprehensive tests, and Fly.io deployment as an opinionated production template. Remix SaaS (community) ships with PostgreSQL, organizations, and shadcn/ui as a feature-complete starter. Both are free. Your choice depends on whether you value practices (Epic) or features (Remix SaaS) out of the box.

Head-to-Head

Epic StackRemix SaaS
Created byKent C. DoddsCommunity
DatabaseSQLite + DrizzlePostgreSQL + Prisma
AuthSessions + TOTP 2FASessions + OAuth
Organizations
TestingFull (Playwright + Vitest + MSW)Basic
EmailResend + templatesResend
UICustom (no UI lib)shadcn/ui + Tailwind
DeploymentFly.io (multi-region)Any Node.js host
DocumentationGoodMinimal
MaintenanceActive (K.C. Dodds)Community

Epic Stack: The Practices First Approach

Epic Stack's core philosophy: ship with the practices that matter before adding features.

// Epic Stack testing approach — every feature tested
// app/routes/settings+/profile.change-password.tsx
import { test, expect } from '@playwright/test';

test('can change password', async ({ page }) => {
  const user = await db.insert(users).values({
    email: `test-${Date.now()}@example.com`,
    passwordHash: await hashPassword('oldpassword'),
  }).returning().get();

  await page.goto('/login');
  await page.fill('[name=email]', user.email);
  await page.fill('[name=password]', 'oldpassword');
  await page.click('[type=submit]');

  await page.goto('/settings/profile');
  await page.click('text=Change Password');
  await page.fill('[name=currentPassword]', 'oldpassword');
  await page.fill('[name=newPassword]', 'newpassword123');
  await page.click('[type=submit]');

  await expect(page.locator('[data-testid=success]')).toBeVisible();
});

Epic Stack ships with 60+ tests covering auth, profile management, and critical user flows. This is rare — most boilerplates ship with zero tests.


Epic Stack's SQLite Philosophy

The SQLite choice is controversial but defensible:

// SQLite with Litefs for production multi-region
// Drizzle schema definition
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';

export const users = sqliteTable('users', {
  id: text('id').notNull().primaryKey(),
  email: text('email').notNull().unique(),
  username: text('username').notNull().unique(),
  name: text('name'),
  createdAt: integer('created_at', { mode: 'timestamp' })
    .notNull()
    .defaultNow(),
});

export const passwords = sqliteTable('passwords', {
  hash: text('hash').notNull(),
  userId: text('user_id').notNull().references(() => users.id, { onDelete: 'cascade' }),
});

Why SQLite in production?

  • Single file = no database server to manage
  • Litefs provides multi-region replication on Fly.io
  • SQLite handles most SaaS workloads to $1M ARR
  • Simpler local development (no Docker needed)

The trade-off:

  • Fly.io becomes a deployment dependency
  • Multi-writer scenarios are complex (Litefs handles reads)
  • PostgreSQL-specific features unavailable

Remix SaaS: Features First Approach

Remix SaaS takes the opposite path — give developers the features they need today:

// Organization management — not in Epic Stack
export async function getOrganizationWithMembers(orgId: string) {
  return prisma.organization.findUnique({
    where: { id: orgId },
    include: {
      members: {
        include: { user: true },
        orderBy: { createdAt: 'asc' },
      },
      subscription: {
        include: { plan: true },
      },
    },
  });
}

// Plan enforcement middleware
export async function requireActivePlan(request: Request) {
  const user = await requireUser(request);
  const org = await getOrganizationByUserId(user.id);

  if (!org?.subscription || !isSubscriptionActive(org.subscription)) {
    throw redirect('/pricing?reason=subscription-required');
  }

  return { user, org };
}

The Key Decision Points

Choose Epic Stack when:

1. Testing is non-negotiable from day one Epic Stack's test suite is genuinely valuable. Bug-free auth flows matter for SaaS.

2. You're deploying to Fly.io Epic Stack's Fly.io integration (Litefs, multi-region) is purpose-built. You'll fight the defaults if deploying elsewhere.

3. You want a reference implementation Kent C. Dodds built Epic Stack as "this is how I'd build a real app." It's opinionated because opinionation is the point.

4. Single-user or per-user SaaS (no organizations) Epic Stack's user-centric model is simpler when you don't need multi-tenancy.

Choose Remix SaaS when:

1. You need organizations now Multi-tenancy is the most-requested feature in any SaaS starter. Remix SaaS ships it; Epic Stack doesn't.

2. You prefer PostgreSQL PostgreSQL is the default for most hosting platforms (Railway, Supabase, Neon, Render). Remix SaaS's Prisma + PostgreSQL is a more standard setup.

3. You want shadcn/ui Remix SaaS ships with the component library most Next.js developers already know.

4. You'll deploy outside Fly.io Remix SaaS is deployment-agnostic. Railway, Render, Heroku, VPS — all work cleanly.


A Middle Path: Epic Stack + Organizations

Some teams start with Epic Stack and add organizations:

// Adding organizations to Epic Stack
// This is ~2 days of work, but well-structured
export const organizations = sqliteTable('organizations', {
  id: text('id').notNull().primaryKey(),
  name: text('name').notNull(),
  slug: text('slug').notNull().unique(),
  createdAt: integer('created_at', { mode: 'timestamp' }).notNull().defaultNow(),
});

export const organizationMemberships = sqliteTable('organization_memberships', {
  userId: text('user_id').notNull().references(() => users.id),
  organizationId: text('organization_id').notNull().references(() => organizations.id),
  role: text('role', { enum: ['owner', 'admin', 'member'] }).notNull(),
});

Epic Stack's clean architecture makes additions easier than most boilerplates.


Verdict

Use Epic Stack if: Quality practices > feature completeness. Fly.io deployment. SQLite is acceptable.

Use Remix SaaS if: Feature completeness > testing rigor. PostgreSQL required. Organization support needed.

Use neither if: You're not committed to Remix. Next.js alternatives have more tooling, more tutorials, and more community momentum.


Compare all Remix and Next.js boilerplates side-by-side on StarterPick.

Check out this boilerplate

View Epic Stack on StarterPick →

Comments