Epic Stack Review 2026: Best Free Remix Boilerplate
TL;DR
Epic Stack is the most opinionated, best-documented free boilerplate. Kent C. Dodds' philosophy shows throughout: the stack prioritizes simplicity, testing, and deployment correctness over feature count. It starts with SQLite (not PostgreSQL), deploys to Fly.io (not Vercel), and ships with Vitest + Playwright fully configured and running. Excellent for learning Remix deeply or for teams already committed to Remix. Less suited for developers who need to swap its opinionated choices.
Rating: 4/5
What You Get (Free)
npx create-epic-app@latest
Core stack:
- Remix (latest) + TypeScript (strict)
- SQLite (development) → Postgres or LiteFS (production)
- Fly.io deployment (pre-configured)
- Auth: Custom (email/password + passkeys + OAuth via GitHub)
- Email: Mailgun or Resend
- UI: Radix UI + Tailwind CSS
- Testing: Vitest unit + Playwright E2E + MSW network mocking
- Monitoring: Sentry + Grafana (pre-configured)
The SQLite Default: Why It's Pragmatic
The most surprising choice: Epic Stack starts with SQLite in development.
// prisma/schema.prisma — SQLite in dev
datasource db {
provider = "sqlite"
url = env("DATABASE_URL") // file:./dev.db locally
}
The reasoning is sound:
- SQLite requires no database service to run locally —
npm run devjust works - For most SaaS apps, SQLite is sufficient until you need horizontal scaling
- LiteFS (Fly.io's distributed SQLite) handles production scaling across multiple nodes
- When you genuinely need PostgreSQL, the Prisma migration is one config change
Kent has published detailed explanations of this choice. The short version: most developers reach for PostgreSQL out of habit, not necessity. SQLite eliminates a category of "works on my machine" database connectivity issues that cost developers hours.
Testing is First-Class
Unlike every other boilerplate, Epic Stack's tests run out of the box with no setup:
npm run test # Vitest unit tests — runs immediately
npm run test:e2e # Playwright E2E — runs against local dev server
// tests/e2e/auth.test.ts — works without modification
import { test, expect } from '@playwright/test';
test('user can register and log in', async ({ page }) => {
const email = `test-${Date.now()}@example.com`;
await page.goto('/');
await page.getByRole('link', { name: 'Sign up' }).click();
await page.getByLabel('Email').fill(email);
await page.getByLabel('Password').fill('SecurePassword123!');
await page.getByRole('button', { name: 'Create account' }).click();
await expect(page).toHaveURL('/dashboard');
await expect(page.getByText('Welcome')).toBeVisible();
});
This is genuinely rare. Most boilerplates ship test configurations but no working tests. Epic Stack includes tests for the authentication flow, form validation, and session management — a working test suite you can run on day one.
MSW (Mock Service Worker) is configured for network request mocking in tests. For testing features that depend on external APIs, MSW intercepts and returns fixtures without hitting real endpoints.
The Authentication System
Epic Stack's auth is custom — not NextAuth or Clerk. This is intentional: Kent wants developers to understand what's happening rather than delegating auth to a library they don't understand.
// app/utils/auth.server.ts — custom session management
import { createCookieSessionStorage, redirect } from '@remix-run/node';
const sessionStorage = createCookieSessionStorage({
cookie: {
name: '__session',
httpOnly: true,
path: '/',
sameSite: 'lax',
secrets: [process.env.SESSION_SECRET!],
secure: process.env.NODE_ENV === 'production',
},
});
export async function requireUserId(
request: Request,
{ redirectTo }: { redirectTo?: string | null } = {}
): Promise<string> {
const userId = await getUserId(request);
if (!userId) {
const requestUrl = new URL(request.url);
redirectTo = redirectTo === null
? null
: redirectTo ?? `${requestUrl.pathname}${requestUrl.search}`;
const loginParams = redirectTo ? new URLSearchParams({ redirectTo }) : null;
const loginRedirect = ['/login', loginParams?.toString()]
.filter(Boolean)
.join('?');
throw redirect(loginRedirect);
}
return userId;
}
Passkeys (WebAuthn) are supported out of the box — rare among boilerplates in 2026. Passkeys are the future of authentication (biometric, device-based, phishing-resistant), and Epic Stack includes the WebAuthn implementation from day one.
Fly.io Deployment
Epic Stack is optimized for Fly.io, not Vercel:
# fly.toml
app = "your-app-name"
primary_region = "iad"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 0
[[vm]]
memory = "1gb"
cpu_kind = "shared"
cpus = 1
fly launch # First deployment
fly deploy # Subsequent updates
fly secrets set SESSION_SECRET=$(openssl rand -hex 32)
The Fly.io preference is architectural: Remix is a server-rendered framework with persistent server state. Vercel's serverless model (cold starts, no persistent state) is less suitable for Remix than Fly.io's persistent VMs. The Epic Stack's deployment is optimized for the framework's model.
Adding Billing (Not Included)
Epic Stack doesn't include Stripe. This is intentional — Kent wants the boilerplate to teach architecture, not bundle every possible integration. Adding Stripe yourself:
// app/routes/api.stripe.webhooks.ts — add this file
import type { ActionFunctionArgs } from '@remix-run/node';
import { stripe } from '~/utils/stripe.server.ts';
import { updateSubscription } from '~/models/subscription.server.ts';
export async function action({ request }: ActionFunctionArgs) {
const sig = request.headers.get('stripe-signature');
const body = await request.text();
let event;
try {
event = stripe.webhooks.constructEvent(
body,
sig!,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch (err) {
return new Response(`Webhook error: ${err.message}`, { status: 400 });
}
switch (event.type) {
case 'customer.subscription.updated':
await updateSubscription(event.data.object);
break;
}
return new Response('ok');
}
Adding Stripe takes about 4-6 hours following Epic Stack's existing patterns.
Epic Stack vs SaaSrock for Remix
For Remix-committed developers, the comparison between Epic Stack and SaaSrock is relevant:
| Factor | Epic Stack | SaaSrock |
|---|---|---|
| Price | Free | $99-$299 |
| Billing | ❌ Add yourself | ✅ Stripe |
| Multi-tenancy | ❌ | ✅ Full |
| Testing | ✅ Complete | Basic |
| Documentation | ✅ Excellent | Adequate |
| Admin panel | ❌ | ✅ Comprehensive |
| SQLite support | ✅ Default | ❌ |
| Learning investment | Lower | Higher |
Epic Stack is the right Remix choice when you want clean architecture and are willing to add SaaS features yourself. SaaSrock is right when you need the complete feature set (multi-tenancy, billing, admin) pre-built.
Permissions and Role-Based Access
Epic Stack includes a permissions system that goes beyond simple auth. The role model uses a capability-based approach where permissions are declared and checked explicitly:
// app/utils/permissions.ts
import type { PermissionString } from './user-validation.ts';
export function userHasPermission(
user: Pick<User, 'roles'> | null,
permission: PermissionString
) {
if (!user) return false;
return user.roles.some(role =>
role.permissions.some(
perm =>
perm.entity === permission.entity &&
perm.action === permission.action &&
perm.access === permission.access
)
);
}
// Using it in a Remix loader
export async function loader({ request, params }: LoaderFunctionArgs) {
const userId = await requireUserId(request);
const user = await prisma.user.findUniqueOrThrow({
where: { id: userId },
select: { roles: { select: { permissions: true } } },
});
const note = await prisma.note.findUniqueOrThrow({
where: { id: params.noteId },
});
// Check if user can update this specific note
if (!userHasPermission(user, 'update:note:any') &&
!userHasPermission(user, 'update:note:own') &&
note.ownerId !== userId) {
throw new Response('Not authorized', { status: 403 });
}
return json({ note });
}
The any vs own access distinction is the critical design: admins can update any note, regular users can update only their own. This pattern scales to any resource type without duplicating permission logic.
Epic Stack's Philosophy: Architecture Over Features
Most boilerplates compete on feature count: "includes auth, billing, email, admin panel." Epic Stack competes on architectural quality. Kent C. Dodds' design principle is explicit: ship fewer features, but ship them correctly.
This shows in the testing setup (tests that actually run), the SQLite choice (pragmatic over conventional), and the missing billing integration (intentional, not an oversight). The bet Epic Stack makes is that developers learn more from a well-structured codebase than from a feature-complete one.
For freelancers and agency developers, this translates differently than for product founders. Agency developers building custom apps benefit from Epic Stack's patterns — the test suite gives clients confidence, the Fly.io deployment is cost-effective for small-to-medium SaaS, and the auth system is understandable enough to audit. Product founders who need to ship fast often find that adding billing from scratch costs more time than the architecture lessons save.
Knowing which camp you're in before evaluating Epic Stack is the key filter.
Who Should Use Epic Stack
Good fit:
- Developers learning Remix who want a well-structured, well-documented reference
- Teams already committed to Remix and Fly.io
- Products where testing is a first-class requirement from day one
- Developers who want to understand every part of their stack (no auth magic)
- Projects where passkeys and WebAuthn are priorities
Bad fit:
- Next.js teams (significant context switch to Remix patterns)
- Founders who need billing working on day one
- Teams who prefer Vercel over Fly.io
- Solo founders who need the fastest time-to-market (SaaSrock or Open SaaS ship faster for feature-dense products)
Final Verdict
Rating: 4/5
Epic Stack is the best-architected free boilerplate. Testing setup that works out of the box, deployment documentation that's actually accurate, SQLite pragmatism that saves hours of local setup, and passkeys support that most boilerplates don't have. The Remix + Fly.io opinions are strong — excellent if they align with your preferences, limiting if you're Next.js-first.
Adding Billing to Epic Stack
Epic Stack intentionally ships without billing — the official documentation recommends adding Stripe yourself rather than prescribing a pattern. This is the right philosophy for a learning resource, but requires real work to turn into a production SaaS.
The typical path: add the Stripe Node.js library, create a billing.server.ts module with customer creation and checkout session helpers, add webhook handling for the checkout.session.completed and customer.subscription.* events, and store subscription status on the user model in SQLite. Because Epic Stack's auth and database patterns are already well-structured, the Stripe integration slots in naturally.
The missing billing integration is the clearest signal that Epic Stack targets developers who understand what they're building, not those who want billing on day one. A solo founder who has never implemented Stripe will spend significantly more time on this integration than someone who has done it before. Account for this in your timeline if you're new to Stripe.
The upside: because you implement billing yourself rather than working around a prescriptive integration, you end up with a billing setup that matches your product's specific needs rather than fighting a boilerplate's opinionated patterns. For products with non-standard billing models (usage-based, seat-based, multiple products), this flexibility is valuable. The explicit requirement to implement billing yourself is also good documentation of what your product actually needs — you can't assume a boilerplate's billing patterns are correct for your use case without understanding them. Epic Stack forces that understanding. This is a rare quality in boilerplates: the missing feature is actually a teaching moment, not an oversight. Most boilerplates hide complexity; Epic Stack exposes it deliberately so you understand what you're building.
Epic Stack's SQLite choice also simplifies billing implementation significantly. The database runs in-process, so billing-related queries (checking subscription status before rendering gated content, counting usage) don't incur network overhead. For read-heavy billing checks (every authenticated page load), the performance difference is meaningful. When the product scales beyond SQLite's capacity, LiteFS or Turso extend the SQLite model to distributed deployments before requiring a full migration to PostgreSQL.
Remix's server-side rendering model means billing checks happen on the server before HTML is sent to the browser — there's no client-side conditional rendering that could be bypassed. Feature gating in Epic Stack is implemented in the loader, not the component, which is architecturally correct for security-sensitive access control. Epic Stack's active maintenance through 2025 — with updates to Remix's latest patterns, testing library improvements, and Fly.io deployment configuration — demonstrates the ongoing curation that separates it from more static boilerplates; it remains the most opinionated free reference implementation for teams who want to learn production Remix patterns alongside building their product.
The boilerplate that works best is the one your team can productively extend. Documentation quality, community activity, and the clarity of the codebase architecture matter as much as the feature list when you're making the decision for a product you'll maintain for years.
Compare Epic Stack with alternatives in our best open-source SaaS boilerplates guide.
See our SaaSrock review — the paid Remix alternative with billing, multi-tenancy, and entity system.
See how Epic Stack's testing compares in our testing in boilerplates guide.
Check out this boilerplate
View Epic Stackon StarterPick →