One canonical door into each technology's own documentation, plus what to notice about the way this repository actually uses it.
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.
Start heretypescriptlang.orgImports here go through the package.json `imports` map (`#app/*`, `#tests/*`) since ADR 046 removed tsconfig paths; this page explains how TypeScript resolves those `#` specifiers.
Route modules import generated `./+types/<route>.ts` types for loader and action arguments, which only exist after `react-router typegen` — the reason `npm run typecheck` runs typegen before tsc and a fresh clone shows type errors until it does.
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.
Start herereact.devThe repo ships a Cursor rule, .cursor/rules/avoid-use-effect.mdc, that links this page and asks for event handlers, ref callbacks and derived data instead of useEffect in new components.
app/entry.server.tsx renders every document with it and switches between onShellReady and onAllReady depending on isbot; this reference explains those callbacks and abort.
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.
Start herereactrouter.comEvery file in app/routes exports some of loader, action, meta, handle, headers and ErrorBoundary; this is the list of those exports and when each one runs.
All mutations here are actions reached by Form or fetcher submissions, several switching on an `intent` field; this page explains how an action's return value and revalidation work.
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.
Start hereexpressjs.comserver/index.ts sets `trust proxy` because Fly terminates TLS in front of it, and keys rate limits on Fly-Client-Ip instead of req.ip; this page explains what trusting the proxy changes.
server/index.ts links this page when it disables x-powered-by, and its helmet headers, HTTPS redirect and rate limiting follow the same checklist.
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.
Start hereprisma.ioUser search is raw SQL in prisma/sql/searchUsers.sql, compiled into a typed function by `prisma generate --sql` — the step setup, CI and litefs.yml all run.
Schema changes are authored with `prisma migrate dev`, while CI and the LiteFS boot sequence run `prisma migrate deploy`; this explains why deploy never generates or resets and is safe on the primary.
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.
Start heresqlite.orgother/litefs.yml switches both the application and cache databases to WAL when the primary boots, to reduce locking between concurrent readers and the writer; this explains what WAL changes.
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.
Start herefly.ioother/litefs.yml uses the fuse, data, proxy, Consul lease and exec sections; this defines each key, including `if-candidate` on the migration command.
The proxy on port 8080 is why POST requests on a replica still write successfully: it forwards write requests to the primary with fly-replay, which GET loaders do not get.
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.
Start herefly.iofly.toml defines the volume mount, service ports, concurrency limits and the http_checks against /resources/healthcheck and /litefs/health; healthcheck.tsx links this page.
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.
Start heretailwindcss.comThere is no tailwind.config file: the type scale, radii and animations are @theme variables in app/styles/tailwind.css and colours are mapped with @theme inline, all explained here.
Themes switch through a `.dark` class on <html>, chosen from the en_theme cookie or client hints and declared with `@custom-variant dark`; this page covers that class-based setup.
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.
Start hereui.shadcn.comThe background and foreground token pairs in tailwind.css follow shadcn's CSS-variable convention, so this page tells you which variables a newly added component will expect.
app/components/ui/README.md points to the CLI, and components.json already maps its output to `#app/components/ui` and `cn` to `#app/utils/misc`.
Downloaded components are edited in place and updated by hand (ADR 019), so they drift from upstream; StatusButton and the Conform-bound fields in forms.tsx are the Epic Stack's own additions on top of them.
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.
Start hereradix-ui.comButton, DropdownMenuTrigger and DropdownMenuItem are used with `asChild` throughout — a Link rendered as a button, a submit button rendered as a menu item — which is the pattern this guide covers.
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.
Start hereconform.guideEvery action parses FormData with parseWithZod, usually async with superRefine for database checks and transform for uploads or login; this covers the async and intent options they rely on.
Forms pass the action's `submission.reply()` back as `lastResult` and spread `getInputProps` into Field; useForm's options explain constraint, onValidate and shouldRevalidate.
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.
Start herevite.devserver/index.ts runs Vite in middlewareMode with appType 'custom' and loads server/app.ts through ssrLoadModule on each request, the setup this guide describes.
vite.config.ts sets an es2022 target, hidden source maps, an asset inlining exception for favicons, the SSR input and the Vitest block; this reference explains each option.
vite.config.ts does more than bundle: a first plugin swaps cache.server.ts for a stub under Vitest (ADR 047), the icons plugin writes the sprite into app/components/ui/icons, and the Sentry plugin loads only for production builds with SENTRY_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.
Start herevitest.devThe test block in vite.config.ts sets include, setupFiles, globalSetup and restoreMocks; this defines why global setup builds base.db once while setup files run in every worker.
setup-test-env.ts spies on console.error and console.warn to fail tests, and a test that expects a logged error must replace that spy's implementation, as the thrown message instructs.
Unit tests sit next to the code they cover, such as auth.server.test.ts and auth.$provider/callback.test.ts, and run against a real SQLite copy per worker plus MSW handlers rather than mocking Prisma.
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.
Start hereplaywright.devtests/playwright-utils.ts extends `test` with login, insertNewUser, navigate and prepareGitHubUser fixtures that create database rows before a test and delete them after.
playwright.config.ts starts `npm run start:mocks` in CI and `npm run dev` locally, reusing a server that is already running; this page covers those webServer options.
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.
Start heremswjs.iotests/mocks/index.ts calls setupServer and listen inside the Node process for both Vitest and `npm run dev`, the integration this page describes.
Server data reaches components through loader data and route props, and pending or optimistic UI is read from navigation and fetchers — the theme toggle is an optimistic fetcher, not client state.
Routes come from folder names through react-router-auto-routes rather than a hand-written config, guards throw `redirect()` or `data(..., { status })`, and `export { action } from './+shared/note-editor.server.tsx'` shares one action between two URLs.
Express is only the outer shell: middleware order in server/index.ts decides redirects, headers and rate limits, and the final handler passes every remaining request to React Router's createRequestHandler in server/app.ts.
auth.server.ts relies on PrismaPromise laziness — the `.catch()` is what triggers the session delete on logout — and links this reference for that behaviour.
A single PrismaClient survives dev reloads through `remember('prisma', ...)` and logs queries slower than 20 ms, and queries select explicit fields by convention, with download-user-data.tsx as the commented exception that uses `include`.
Explains how an instance knows whether it is the primary — the same question litefs-js answers for cache.server.ts and ensurePrimary before a write.
The app talks to LiteFS through litefs-js: entry.server.tsx reports primary and current instance in response headers, the OAuth callback calls ensurePrimary() before writing, and replica cache writes are forwarded to the primary over HTTP.
Colours are semantic pairs such as `bg-muted text-muted-foreground` whose values flip under `.dark`; the only Tailwind palette colours in app/ are the violet logo tiles on the marketing page, and class lists are merged with `cn()` so a className prop can override a primitive.
Schemas are shared by client and server — note-editor.tsx exports NoteEditorSchema for useForm's onValidate and the action's parseWithZod — and errors render without JavaScript because actions return `submission.reply()`.
Specs authenticate by writing a Session row and cookie directly instead of using the login form, navigate with React Router's typed `href()` so a renamed route breaks the test at compile time, and clean up their users afterwards.