Start here

Project structure

Where every kind of file in a kitr app lives, what it is called, and the command that writes it.

A kitr app has one place for each kind of file. A controller is always in server/controllers/, a layout always in app/layouts/, a hook always in app/hooks/. This page is that list, with the naming rules and the kitr make command for each kind, so you never have to decide where something goes.

The one rule behind it: code that imports React lives in app/; code that imports @kitr/api, @kitr/server, @kitr/jobs, @kitr/mail or Node lives in server/; a declaration both sides read (a zod schema, an error class, a realtime channel) lives in app/ next to its kind.

The tree

A fullstack app, with every folder a package can bring:

my-app/
├── kitrrc.ts                 # packages, providers, platforms: the one place wiring is declared
├── config/<package>.ts       # one options file per package (config/api.ts, config/jobs.ts, ...)
├── app/                      # the application: bundled by Vite, runs in the browser and in Start
│   ├── app.ts                # createApp(): providers, config and env
│   ├── env.ts                # environment variables, validated at startup
│   ├── routes.ts             # the page table: every .page() and .layout(), with loaders and guards
│   ├── router.tsx            # creates the router from app/routes.ts
│   ├── document.tsx          # the HTML document around every page
│   ├── api/                  # client.ts, the typed client for server/routes.ts
│   ├── pages/                # one component per route; the file path is the URL path
│   ├── layouts/              # components that wrap a group of pages through <Outlet />
│   ├── components/           # reusable React: ui/ (copied by kitr ui add), forms/, <feature>/
│   ├── hooks/                # use-*.ts
│   ├── stores/               # client-only state
│   ├── queries/              # typed queries and mutations, one file per resource
│   ├── actions/<feature>/    # server functions the client calls by name
│   ├── validators/           # zod schemas shared by forms, actions and controllers
│   ├── exceptions/           # KitrError subclasses
│   ├── providers/            # your own lifecycle providers
│   ├── channels/             # realtime channel definitions
│   ├── lib/                  # plain functions and constants that are none of the above
│   ├── locales/              # translation bundles, one per language
│   ├── content/              # static content modules a site renders
│   ├── mocks/                # MSW handlers for development without a backend
│   └── styles/               # app.css and tokens
├── server/                   # the standalone server: Node, never React
│   ├── main.ts               # createServer({ providers, config, routes })
│   ├── routes.ts             # endpoints and sockets: .get, .post, .resource, .ws
│   ├── controllers/          # classes whose methods answer routes
│   ├── services/             # plain classes with business logic, bound in the container
│   ├── middleware/           # defineMiddleware around controllers
│   ├── jobs/                 # background work
│   ├── mail/                 # email templates
│   └── db/                   # schema, migrations and seeders, owned by your ORM
├── platforms/<name>/         # native shells, when a desktop or mobile platform is added
├── test/                     # app-level tests; unit tests may sit beside their file
├── stubs/                    # generator templates you ejected (kitr eject stub <kind>)
├── .kitr/                    # generated types (kitr types); import them from #kitr/*
└── AGENTS.md                 # these rules for coding agents, kept current by kitr add

No project starts with all of it. A blank project is kitrrc.ts, package.json, tsconfig.json, biome.json, .env.example, .gitignore and AGENTS.md; every folder arrives with the package or the generator that needs it. Run kitr make in your project to see the kinds it has, with the path each one writes:

kitr make
#   make:query       app/actions/<feature>/<name>.ts          A server function the client reads by name
#   make:component   app/components/<name>.tsx                A reusable piece of UI with no route of its own.
#   make:layout      app/layouts/<name>.tsx                   A component that wraps a group of pages through <Outlet />
#   make:controller  server/controllers/<name>-controller.ts  A class whose methods answer routes
#   ...

The three files that wire everything

kitrrc.ts lists the kitr packages in the project and their providers. If a package affects your app at startup, it's in this list.

config/<package>.ts holds one package's options, typed by that package's defineConfig. config/query.ts configures Query, config/http.ts configures HTTP, and so on.

app/env.ts declares your environment variables. Public ones (prefixed VITE_) reach the browser; server ones throw if browser code reads them. A missing or malformed variable stops the app at startup with a clear message, not halfway through a request.

app and server

app/ is everything Vite bundles. It runs in the browser, and with a web platform in ssr or static mode it also runs in TanStack Start on the server. Server functions in app/actions/ live here too: the client imports them by name, and Start runs their handlers on the server.

server/ is the standalone server that kitr dev api runs (server/main.ts). It never imports React. It imports from app/ freely: a controller validates with #app/validators/orders, throws #app/exceptions/payment-failed-error, publishes on #app/channels/orders.

A server app (no React at all) keeps the same split. Its app/ holds only env.ts and the shared declarations, so a web or mobile platform can be added later without moving a file. See Build an API server without React.

Kinds

What each kind is, where it lives and what it exports. <name> is kebab-cased as typed: kitr make:service BillingUsage writes billing-usage-service.ts.

In app/

Kind What it is File Export Command
page The component a route renders; options (loader, guards, head) live on its link in app/routes.ts. app/pages/<path>.tsx export default function ProjectsPage make:page <path>
layout A pathless component that wraps a group of pages through <Outlet />; guards go on its route. app/layouts/<name>.tsx export default function AdminLayout make:layout <name>
component A reusable piece of UI with no route of its own. app/components/<name>.tsx export function ProjectCard make:component <name>
form A component that owns one schema-validated form and its submit. app/components/forms/<name>-form.tsx export function ProjectForm make:form <name>
hook A use* function that packages React state or effects for reuse. app/hooks/use-<name>.ts export function useDisclosure make:hook <name>
store Client-only state. Server data belongs in queries, never here. app/stores/<name>.ts [useCartStore, CartStoreProvider, cartStore] make:store <name>
queries Typed queries and mutations for one HTTP resource, with their cache keys. app/queries/<resource>.ts projectsKeys, projectsListQuery, updateProjects make:queries <resource>
query, mutation A server function the client calls by name (useQuery('projects.list')). app/actions/<feature>/<name>.ts export const list = defineQuery(...) make:query <feature>/<name>, make:mutation
validator A zod schema and its type, shared by a form, an action and a controller. app/validators/<name>.ts projectSchema, createProjectSchema, ProjectInput make:validator <name>
exception A KitrError subclass with a code, turned into a typed error on every transport. app/exceptions/<name>-error.ts export class PaymentFailedError make:exception <name>
provider A lifecycle object: register bindings, boot, wrap the React tree. Listed in kitrrc.ts. app/providers/<name>-provider.ts export default defineProvider(...) make:provider <name>
channel A realtime channel: params, events with cache effects, and who may join. app/channels/<name>.ts export const chatChannel make:channel <name>
lib Plain functions and constants: formatting, calculations, glue. app/lib/<name>.ts named exports none
locale A translation bundle; the keys are typed from en. app/locales/<lang>.ts export default { ... } none

In server/ and test/

Kind What it is File Export Command
controller A class whose methods answer routes; validation goes in static validation. server/controllers/<name>-controller.ts export default class UsersController make:controller <name> [--resource]
service A plain class holding business logic, bound in the container by a provider. server/services/<name>-service.ts export class BillingService make:service <name>
middleware A function that runs before and after a controller: auth, audit, rate limits. server/middleware/<name>.ts export const audit = defineMiddleware(...) make:middleware <name>
job A background unit of work with a payload schema, retries and an optional schedule. server/jobs/<name>.ts export const sendReceipt = defineJob(...) make:job <name>
mail A React email template with its subject and preview props. server/mail/<name>.tsx export const welcomeMail = defineMail(...) make:mail <name>
model, migration, seeder Your ORM's own files. kitr connects the ORM (config/db.ts) and stays out of its folder. server/db/ the ORM's the ORM's CLI
test A Vitest file. App-level tests go in test/; a unit test may sit beside its file as <name>.test.ts. test/<name>.test.ts make:test <name>

Packages add kinds when you add them: make:job exists once kitr add jobs has run, make:channel once kitr add realtime has. kitr make lists what your project has.

Naming

  • Files are kebab-case, on both sides: order-summary.tsx, send-receipt.ts, health-controller.ts. It matches the components kitr ui add copies and the URLs pages map to.
  • A file carries the kind's suffix exactly when its export does. UsersController lives in users-controller.ts, BillingService in billing-service.ts, PaymentFailedError in payment-failed-error.ts, FlagsProvider in flags-provider.ts, ProjectForm in project-form.tsx. A file whose one export comes from define*() has no suffix, because the folder already names the kind: server/jobs/send-receipt.ts, server/mail/welcome.tsx.
  • Pages are named by their URL. /projects/$id is app/pages/projects/id.tsx, even though the component inside is ProjectsIdPage.
  • Exports follow what they are. Classes and components are PascalCase; define*() results, schemas and functions are camelCase; hooks start with use. Controllers and pages are the only default exports, because routes import them lazily.

Group by feature inside a kind

A kind folder is flat until it gets crowded. Then give the file a feature folder by typing a nested name:

kitr make:controller admin/users     # server/controllers/admin/users-controller.ts
kitr make:component projects/card    # app/components/projects/card.tsx
kitr make:query projects/list        # app/actions/projects/list.ts
kitr make:layout settings            # app/layouts/settings.tsx

First time? Run npm i -g @kitr/kitr once. Inside a project, the global kitr runs your project's own version.

The same projects/ folder in each kind is the feature. A feature never gets its own top-level folder with every kind mixed inside: a reader looking for a hook looks in app/hooks/, always.

How far the table scales

One route table keeps exact types as it grows: params, loader data and output stay typed per route, and keyof typeof routes.$types lists every name. Chain as many .page(), .get() and .group() calls as the app needs. .merge() joins tables from separate files, which is how server/routes.ts reaches app/routes.ts.

300 pages type-check in about 4 seconds on a laptop. Two routes with the same name throw an error that names the duplicate.

Imports

Five aliases reach every top-level folder, in TypeScript, in Vite and in Node (they are subpath imports in package.json):

Alias Folder
#app/* app/
#components/* app/components/
#server/* server/
#config/* config/
#kitr/* .kitr/ (generated)

Import across folders with an alias (#app/validators/orders) and inside one folder with a relative path (./order-row). Never ../ across folders: the alias says where the file lives.

Generators

kitr make:<kind> <name> writes one file from a template and, where the kind is registered somewhere, registers it: make:page and make:layout add their link to app/routes.ts, make:provider adds the provider to kitrrc.ts, and make:controller prints the route line to paste into server/routes.ts.

Flag Effect
--dry-run Prints what it would write and register; writes nothing.
--force Overwrites an existing file.
--resource make:controller only: index, show, store, update and destroy with zod validation.

kitr eject stub <kind> copies a template to stubs/<kind>.ts; every make:<kind> after that uses your copy. kitr make --json prints the list for scripts and agents. The full command list is in the CLI reference.

Tests

App-level tests (boot the app, call the API, run a flow) go in test/, where the kits' Vitest config and tsconfig.json already look. A unit test may sit beside the file it tests instead: app/components/orders/order-row.test.tsx. kitr make:test <name> writes the first kind. See Testing.