Tool agnostic context and working conventions for this repository. This is the primary source of truth for any coding agent (Claude Code, Codex, Cursor, etc.) working in this codebase. Read this before making changes.
tanstack-starter is a full stack starter template built on
TanStack Start. It bundles
authentication, a database layer, localization, UI components, and SSR out
of the box.
Core stack:
@tanstack/react-startwith@tanstack/react-routerfor routing and SSRreact19drizzle-ormwith PostgreSQL (pg)better-authfor authentication@inlang/paraglide-jsfor i18nresendfor emailtailwindcssv4 withshadcn/uistyle componentsnitroas the server runtimeviteas the build tool
This project uses pnpm (see packageManager in package.json and
pnpm-lock.yaml). Prefer pnpm for all commands. npm, bun, and deno
equivalents exist in docs/commands/ for contributors who need them, but
default to pnpm unless the user specifies otherwise.
Run these from the repository root:
pnpm install- install dependenciespnpm dev- start the dev server (vite dev)pnpm build- production build (vite build)pnpm serve- preview a production build (vite preview)pnpm start- run the built server (node .output/server/index.mjs)pnpm migrate- run database migrations (drizzle-kit migrate)pnpm migrate:revert- drop/revert migrations (drizzle-kit drop)pnpm validate- type check, lint, and format check (tsc && oxlint && oxfmt --check .)pnpm fix- auto fix unused exports, lint issues, and formatting (pnpm knip && oxlint --fix . && oxfmt --write .)pnpm knip- find and remove unused files, exports, and dependencies
There is no test runner configured in this repository. Do not invent a test
command; rely on pnpm validate to catch type and lint errors.
Before considering a change complete, run pnpm validate. A pre-commit hook
(.husky/pre-commit) already runs this on commit, so failures there block
the commit.
src/
client.tsx entry point for the client bundle
server.ts entry point for the server bundle
router.tsx TanStack Router setup
start.tsx TanStack Start setup
routeTree.gen.ts generated route tree, do not edit by hand
routes/ file based routes, including src/routes/api for API routes
typedefs/ shared *.d.ts declaration files
lib/
auth/ better-auth config, client, hooks, and utils
components/ shared UI components, one PascalCase folder per component
constants/ shared constants
controllers/ request/business logic controllers
database/ drizzle schemas, migrations, providers, and config
email/ resend config and email utilities
i18n/ paraglide config and translations (compiled/ is generated)
layouts/ page layout components
middleware/ TanStack Start middleware
pages/ top level page components, one PascalCase folder per page
services/ service layer, external integrations
styles/ global CSS, Tailwind entry point
types/ shared TypeScript types
ui/ shadcn/ui style primitives
utils/ shared utility functions
Generated or compiled directories should never be hand edited:
routeTree.gen.ts, src/lib/i18n/compiled/, src/lib/i18n/config.inlang/,
.output/.
Import with the @/ aliases defined in tsconfig.json rather than deep
relative paths:
@/*->src/*@/auth/*->src/lib/auth/*@/components/*->src/lib/components/*@/constants/*->src/lib/constants/*@/controllers/*->src/lib/controllers/*@/database/*->src/lib/database/*@/email/*->src/lib/email/*@/i18n/*->src/lib/i18n/*@/layouts/*->src/lib/layouts/*@/middleware/*->src/lib/middleware/*@/pages/*->src/lib/pages/*@/services/*->src/lib/services/*@/styles/*->src/lib/styles/*@/types/*->src/lib/types/*@/ui/*->src/lib/ui/*@/utils/*->src/lib/utils/*@/package->package.json
- TypeScript strict mode is on. Do not weaken
tsconfig.jsonsettings to silence errors, fix the underlying type issue instead. - Components and pages live in one PascalCase folder per component under
src/lib/components/orsrc/lib/pages/(for examplesrc/lib/components/NavBar/NavBar.tsx). - Formatting and linting are enforced by
oxfmtandoxlint(oxfmt.config.ts,oxlint.config.ts), not Prettier or ESLint. Let these tools format code, do not hand format against their rules. jsx-a11yrules are enforced as errors, keep markup accessible (alt text, labels, keyboard handlers, valid roles, etc).- Tailwind classes are linted for canonical form, shorthand, and sort order.
Let
oxlint --fixorpnpm fixreorder classes rather than hand ordering them. knipenforces that files, exports, and dependencies stay used. If you add something intentionally unused for now, use thelintignoretag rather than leaving it to be flagged.
- Schemas live in
src/lib/database/schemas, migrations insrc/lib/database/migrations, generated and applied viadrizzle-kit(drizzle.config.ts). - After changing a schema, generate a migration with
drizzle-kitand apply it withpnpm migrate. Do not hand edit generated migration SQL. - A local Postgres instance is expected;
docker-compose.yamlspins one up withdocker compose -f docker-compose.yaml up --build -d.
Configuration is read from .env. .env.example documents every variable
that is required. When adding a new environment variable, add it to
.env.example with a description, never commit real secrets.
Commit messages are linted by commitlint (.commitlintrc.json, enforced via
the .husky/commit-msg hook) using Conventional Commits with this allowed
type list: feat, fix, docs, chore, style, refactor, ci, test,
revert, perf.
Example: fix(auth): correct redirect after session expiry
Translation source files live in src/lib/i18n/translations, compiled
output lives in src/lib/i18n/compiled and is generated by paraglide, do not
edit it directly. Regenerate it through the paraglide tooling instead of
editing compiled output.
See .github/CONTRIBUTING.md for the contribution process. In short: discuss
non trivial changes before making them, update README.md when interfaces or
environment variables change, and follow SemVer for version bumps.