The Epic Stack is Kent C. Dodds's opinionated full-stack starter and reference application, and this repository is the template itself. It ships as a working app called Epic Notes: server-rendered React Router 7 on an Express server, SQLite through Prisma replicated across Fly.io machines with LiteFS, and notes whose images live in S3-compatible Tigris storage. Around that deliberately small domain it implements the parts most products need and few get right on the first try: email signup with emailed one-time codes, password reset, GitHub OAuth, passkeys, TOTP two-factor, role-based permissions, a two-tier cache, tiered rate limiting, and a GitHub Actions workflow that tests every change and deploys `main` to production and `dev` to staging. `npx epicli` generates a new project from it and runs remix.init to rename the app and optionally provision Fly.
Continue onboarding
Build a mental model of the system before changing it.
Developers who generated an app from the Epic Stack (or are evaluating it) and need to know where authentication, data access, styling and deployment live before they change them, and contributors to the template itself, whose design decisions are recorded one per file in docs/decisions.
Every module in app/, server/ and tests/ is TypeScript (ADR 001 makes the stack TypeScript-only), and imports resolve through the package.json `#app/*` and `#tests/*` subpath map rather than tsconfig paths.
Deploy target: fly.toml defines the volume, service ports and health checks, and the GitHub workflow builds the image with flyctl and deploys `main` to the app and `dev` to its `-staging` twin.
Framework mode v7: route modules export loader, action, meta, handle and ErrorBoundary; `react-router build` produces build/server and build/client; react-router-auto-routes turns the app/routes folder tree into the route manifest.
The HTTP server in server/index.ts: HTTPS and trailing-slash redirects, compression, security headers, three rate-limit tiers and static assets, before handing every request to @react-router/express.
Renders every route module on the server with renderToPipeableStream in app/entry.server.tsx and hydrates in the browser; UI state is kept in forms, fetchers and the URL rather than client-side stores.
ORM for the SQLite application database: schema in prisma/schema.prisma, migrations in prisma/migrations (the initial one also inserts the admin and user roles), and TypedSQL for the raw user-search query in prisma/sql.
Version 4, configured entirely in app/styles/tailwind.css: semantic colour custom properties with light and `.dark` values, a named type scale and animations declared with @theme, and a class-based dark variant.
Headless behaviour and accessibility under the checkbox, dropdown menu, label and tooltip primitives, plus the Slot that gives Button its `asChild` prop; only files in app/components/ui import it.
Progressively enhanced forms: every action validates FormData with parseWithZod, and useForm wires field props and the action's `submission.reply()` errors back into the Field components.
Both datastores: Prisma's application database and a separate cache database that app/utils/cache.server.ts opens directly with node:sqlite; other/litefs.yml puts both in WAL mode when a deployed primary boots.
Replicates the SQLite files across Fly machines: other/litefs.yml elects a primary through Consul, runs migrations only on the primary candidate, and fronts the app with a proxy that forwards write requests to the primary.
The source registry behind app/components/ui: components.json points its CLI at the Tailwind file and `#app/components/ui`, and downloaded components are edited in place rather than updated as a dependency.
Builds the app through the React Router plugin, runs as Express middleware in development, generates the SVG icon sprite, and loads the Sentry plugin only for production builds that have an auth token.
Unit and component tests colocated as app/**/*.test.ts(x), each worker copying a pre-migrated SQLite file and failing any test that logs console.error or console.warn.
End-to-end tests in tests/e2e, run in CI against the built app started with `npm run start:mocks`, using fixtures that insert a user and a session cookie instead of clicking through login.
Intercepts Resend, GitHub, Tigris and Have I Been Pwned calls both in tests and in `npm run dev` (MOCKS=true), so the app runs offline with the fake values in .env.example.