What each piece is, why the template uses it, and what could go wrong with it. Versions are pinned exactly in
package.json; the ADRs hold the longer reasoning.
| Piece | Why it is here | Risk |
|---|---|---|
| Node 26, pnpm 12 | pnpm blocks dependency build scripts unless allowed, holds back releases younger than a day and refuses a package whose trust level drops (pnpm-workspace.yaml) |
Low |
| TypeScript 7 | The native compiler keeps pnpm check fast. It has no JavaScript compiler API, so tools that need one run on workarounds (ADR 0002) |
Medium |
| TanStack Start and Router, Vite 8 | Server rendering, file routes and server functions in one Node service, with typed loaders and search params | Medium: Start calls itself a release candidate |
| Nitro 3 | The deployment layer: the production server, graceful shutdown, prerendered static pages (ADR 0004) | Medium: beta |
| React 19 with the React Compiler | Memoization without hand-written useMemo, through the stable Babel preset (ADR 0006) |
Low |
| Tailwind 4, shadcn/ui on Radix | Accessible primitives copied into src/components/ui/, styled with classes only, which the CSP requires (ADR 0010) |
Low |
Effect 4 HttpApi |
The API contract: schemas, endpoints and typed errors in src/contract, the OpenAPI document generated from it, and handlers tested without a database (ADR 0001, ADR 0007) |
Medium: 4.0.0 is the first stable release of v4, and most published examples are still v3 |
| Hey API, TanStack Query | The client generated from openapi.json: fetch functions, query options and MSW handlers |
Medium: a next snapshot until a stable release supports TS 7 |
| TanStack Form with Effect Schema | Forms validate with the same schemas the server checks, loaded on the first interaction | Low |
| Better Auth | Email and password sessions, verification, reset and session management. Account actions run through server functions, and rate limits are counted in Postgres (ADR 0003, ADR 0013) | Medium: several workarounds, listed below |
| Postgres 18, Drizzle ORM 0.45 | SQL-shaped queries with typed results, uuidv7() ids, migrations generated by drizzle-kit and linted by squawk (ADR 0005, ADR 0014) |
Low |
| Oxlint with tsgolint, Oxfmt, Fallow | Type-aware lint, formatting, dead code, architecture boundaries, complexity and duplication, all fast enough for every commit | Low; Oxfmt is 0.x |
| Vitest 5, Playwright, axe | Unit, API, component (browser mode) and integration tests in Vitest; end-to-end and accessibility tests in five browser projects | Low |
| Lighthouse behind Caddy | Page performance measured over HTTPS and HTTP/2, as in production (ADR 0011) | Medium: GitHub's runners score an unchanged page 99 on mobile |
| lefthook, Renovate, Betterleaks, grype, zizmor | Pre-commit jobs, dependency and image-digest updates, secret scanning, image scanning and workflow linting | Low; Betterleaks is a 2.0.0 release candidate |
The pre-release pieces (TanStack Start, Nitro, Oxfmt, Hey API) are upgraded one per pull request with
every gate passing (skill upgrade-prerelease-deps). Most of the churn stays in a few files: the Effect HTTP
imports (effect/http-api, effect/http), for example, appear only in src/contract, src/server/api and
scripts/openapi.ts.
Code that exists because of an upstream bug or missing feature links the issue next to it. These are the ones that change something here when they are fixed:
| Upstream | What changes here |
|---|---|
| hey-api/hey-api#4235 | Leave the next snapshot for a stable release (ADR 0002) |
| typescript-eslint/typescript-eslint#10940 | Drop the peer rule for the TanStack lint plugins in pnpm-workspace.yaml |
| better-auth/better-auth#11078 | Replace the endpoint allowlist plugin (src/server/http/auth-endpoints.ts) with enabledPaths |
| better-auth/better-auth#11433 | Lower the /revoke-other-sessions budget in tests/db/query-budget.test.ts to a constant |
| better-auth/better-auth#9920 | Generate the auth schema instead of maintaining src/server/db/schema/auth.ts by hand |
| TanStack/router#8246 | Unknown server function ids answer 404 (ADR 0009) |
| TanStack/router#7473 | Revisit prerendering through Nitro (ADR 0004) |
| TanStack/router#8520 | Route chunks preload their static imports; re-run pnpm lighthouse (ADR 0011) |
| TanStack/form#2132 | Remove src/lib/no-devtools-event-client.ts and its alias in vite.config.ts |
| GoogleChrome/lighthouse#16539 | Mobile scores stop counting preloaded scripts against first paint; CI goes back to the target bar |
| A Nitro release with nitrojs/nitro#4620 and #4654 (merged after 3.0.260903-beta) | Consider Nitro's maxRequestBodySize in place of the bodyLimit middleware in src/start.ts |
| drizzle-team/drizzle-orm#3807, #5347 | Simplify scripts/check-drift.ts (absolute --out, UNLOGGED tables) |
| microsoft/playwright#42731 | Remove the Firefox COOP preference in playwright.config.ts after Playwright 1.63 |
| evilmartians/lefthook#1167 | Run the security job of .config/lefthook.yml inline instead of through a pnpm script, once a release has the fix |
| oxc-project/oxc#17021, #23695 | Lint directives through Oxlint instead of tests/unit/repo-policy.test.ts; the plain plugin specifier in .oxlintrc.json |
| stryker-mutator/stryker-js#6210 | Add mutation testing as a gate (below) |
StrykerJS 10's Vitest runner builds its per-test name filter in a way Vitest 5 no longer matches, so every
test inside a describe is skipped and every mutant survives. Once a release fixes it, a pnpm mutate script
can run Stryker on the files a pull request changes and on all of src/ nightly, with these minimum scores:
| Files | Minimum score |
|---|---|
Every module in COVERAGE_GATE (vitest.config.ts) except the two below |
100 |
src/features/auth/utils/describe-session.ts, src/features/auth/utils/safe-redirect.ts |
90 |
src/features/*/components/**, src/components/errors/** |
80 |