kitdocs · github

Default stack

raw markdown reference/stack.md verified 2026-09-26

Versions are what two production apps actually ran on 2026-09-26 (^ = caret range in package.json). Pin what is marked pinned. For the rest, stay on the major shown until you choose to move.

App with accounts (api/ + web/)

api/

Package Version Why
Node 22 LTS on the server run built JS: node --max-old-space-size=128 dist/server.js
fastify ^5.12 fast, schema-friendly, small memory footprint
@fastify/cookie ^11.1 session cookie
@fastify/rate-limit ^10.3 login/reset limits (key on the real client IP header, see new-web-app.md)
prisma + @prisma/client 6.19.3 pinned migrations + typed client. Prisma 7/8 are out and the CLI nags about them on every run; ignore it until you choose to migrate on purpose
zod ^4.6 validate every request body
@node-rs/argon2 ^2.2 password and reset-code hashing (native, no build step)
typescript ~5.9 pinned in api/ the Prisma 6 toolchain is proven on 5.9
tsx dev only local watch mode. Never in production
esbuild (or tsc) build bundle src/ → dist/ for production

web/

Package Version Why
react, react-dom ^19.2
vite ^8.3 dev server + static build
tailwindcss + @tailwindcss/vite ^4.3 v4: no config file, one Vite plugin, @import "tailwindcss"
shadcn (CLI) + radix-ui ^4.21 / ^1.6 components copied into components/ui/, preset radix-nova
cn ^0.2–0.3 the radix-nova preset imports cn from this npm package (shadcn's own). It's legitimate, not a stray dependency
class-variance-authority, tw-animate-css ^0.7 / ^1.4 variants and animations shadcn expects
@tanstack/react-query ^5.102 server state, caching, retries
react-router-dom ^7.18 routing
lucide-react ^1.45 icons
sonner ^2.0 toasts
date-fns ^4.4 dates (no moment)
idb-keyval ^6.3 small IndexedDB store for offline queues/snapshots
next-themes ^0.4 light/dark switch when needed
vite-plugin-pwa + workbox-* ^1.3 / ^7.4 installable app, precache, runtime caching
vitest ^4.1 unit tests
typescript ~6.0 in web/
oxlint ^1.81 fast lint

Optional, per product: recharts (charts), fuse.js (fuzzy search), tesseract.js (in-browser OCR, but self-host its worker/wasm/language files, see workflows/pwa-and-caching.md), @fontsource/* (self-hosted fonts).

Database

PostgreSQL 16+ bound to localhost only, one database and one role per app. Tests use a real Postgres database per test run (<app>_test), or one per parallel agent.

Static site / marketing page

Option Use when
Vite + React + Tailwind v4 + shadcn + motion one-page sites, portfolios, funnels; output is static files served by Nginx
Next.js static export (output: "export") + Tailwind v4 + motion multi-page content sites, several languages (/ar/… RTL), per-page metadata
Lenis weighted smooth scroll for "editorial" sites; pair with motion's whileInView

Static sites need no Node process on the server: build locally, rsync dist//out/.

Why this stack

Retrofitting Tailwind v4 into an app with hand-written CSS

Tailwind v4 emits cascade layers, and any unlayered rule beats any layered rule whatever its specificity. So old CSS silently wins. During the move, import the old stylesheet into the lowest layer:

@layer legacy, theme, base, components, utilities;
@import "./index.css" layer(legacy);
@import "tailwindcss/theme.css" layer(theme);
@import "tailwindcss/utilities.css" layer(utilities);   /* preflight left out on purpose until the end */

Convert screen by screen, then delete the old file and switch to @import "tailwindcss".

Environment traps