A KosmoJS project has four top-level directories, and each one has exactly one job. Once that clicks, the rest of the docs read much faster.
my-app/
├── package.json → project settings: distDir, devPort, scripts
├── tsconfig.json → { "extends": "./lib/tsconfig.json" }
│
├── src/ ✍️ YOU WRITE THIS
│ ├── front/ → one source folder
│ └── admin/ → another, fully independent
│
├── lib/ 🤖 GENERATED - never edit, don't read to learn
│ ├── tsconfig.json → base config the root tsconfig extends
│ ├── front/ → generated code for src/front
│ └── admin/ → generated code for src/admin
│
├── dist/ 📦 BUILD OUTPUT
│ ├── front/
│ └── admin/
│
└── var/ 🗑️ Vite cache - disposableThe rule of thumb: you own src/, KosmoJS owns lib/. Every _/ import you write points into lib/.
Inside a Source Folder
A source folder is a self-contained app. This is one with a Hono backend and a React frontend, with SSR enabled - the fullest shape:
src/front/
├── kosmo.config.ts → what this folder is
├── tsconfig.json → { "extends": "../../lib/front/tsconfig.json" }
├── index.html → Vite's HTML entry
├── app.tsx → global wrapper, wraps EVERY route
├── router.ts → routerFactory: routes ➜ native router
│
├── api/ ── server side ───────────────────────────
│ ├── app.ts → the backend app instance (appFactory)
│ ├── server.ts → standalone server entry
│ ├── dev.ts → dev hooks: requestHandler, teardownHandler
│ ├── errors.ts → THE central error handler
│ ├── use.ts → global middleware (runs for every route)
│ ├── env.d.ts → global context/state types, custom UseSlots
│ └── users/
│ └── [id]/
│ ├── index.ts → the route ➜ /api/users/:id
│ └── types.ts → colocated helper, NOT a route
│
├── pages/ ── client side ───────────────────────────
│ ├── 404.tsx → rendered for unmatched routes
│ ├── index/
│ │ └── index.tsx → the route ➜ /
│ └── users/
│ ├── layout.tsx → wraps everything under /users
│ └── [id]/
│ └── index.tsx → the route ➜ /users/:id
│
├── components/
│ └── Link.tsx → generated typed Link
│
└── entry/
├── client.ts → mount / hydrate in the browser
└── server.ts → renderToString / renderToStream (SSR only)Things worth noticing:
api/andpages/are siblings with parallel trees, so an endpoint and its page are always one folder apart. Neither directory name appears in a URL.- Only
index.*is a route. Everything else in a route folder is a colocated helper. Rationale › app.*is not a layout. It sits at the folder root and wraps every route;layout.*files live insidepages/**/*and wrap a subtree. Details ›use.tsis not a route either. Drop one in anyapi/folder and it wraps that subtree. Details ›
A folder created with --no-backend simply has no api/; one created with --no-framework has no pages/, app.*, router.ts, index.html or entry/.
Inside lib/
You never edit lib/, but knowing what lives there makes the _/ imports legible. This is a React + Hono folder; exact filenames vary by framework, while the _/ names you import stay the same:
lib/
├── tsconfig.json → shared base for the root tsconfig
├── .gitignore → why lib/ is NOT in the root .gitignore
└── front/
├── tsconfig.json → this folder's jsxImportSource + path mappings
├── api.ts → _/api defineRoute, use
├── api:factory.ts → _/api:factory appFactory, devSetup, errorHandlerFactory
├── app.tsx → _/app the AppProvider seam
├── router.tsx → _/router routerFactory, createRouters
├── query.ts → _/query TanStack client (when enabled)
├── use.ts → _/use framework hooks (Vue/Svelte/MDX)
├── entry/
│ ├── client.ts → _/entry/client
│ └── server.ts → _/entry/server
├── fetch/ → _/fetch typed clients + ResponseT
└── @api/
└── routes.ts → the RouteMaplib/ has its own .gitignore - don't add it to the root one
The root .gitignore deliberately leaves lib/ alone, because lib/.gitignore handles it more precisely: it ignores everything except cache.json and types.ts at any depth.
So generated code is not committed - it is a build artifact. What is committed is the per-route generation cache (cache.json, keyed by content hashes of the route file and its type dependencies), so a fresh clone or a CI run doesn't pay for a full rebuild.
Adding lib/ to the root ignore file would drop that cache and make every clone slow.
Deleting lib/ is safe but not free: it forces a full regeneration, which on a large project takes minutes. Details ›
Path Mappings
Three prefixes are reserved. Don't reuse them for your own aliases.
| Prefix | Resolves to | Use it for |
|---|---|---|
@/* | project root | anything shared across source folders - db layer, domain types |
~/* | this source folder | your own modules inside the folder |
_/* | lib/<this folder>/ | generated code |
import { db } from "@/db"; // my-app/db.ts
import type { User } from "~/types/user"; // src/front/types/user.ts
import { defineRoute } from "_/api"; // lib/front/api.ts
import fetchClients from "_/fetch"; // lib/front/fetch/~/ and _/ are folder-relative: the same _/api in src/admin resolves to lib/admin/api.ts. That is what keeps folders isolated - admin's route names and navigation types never leak into front.
@/ is how folders share without packages. Put a type in db/models.ts, import it as @/db/models from every folder, change it once, and every folder sees it - no workspaces, no publishing, no version bumps.
Build Output
dist/front/
├── api/
│ ├── app.js → the app instance, for custom mounting
│ └── server.js → ready-to-run API server
├── client/
│ ├── assets/ → hashed JS, CSS, images
│ └── index.html
└── ssr/ → only when SSR is enabled
├── app.js
├── server.js → serves pages AND the API
└── assets/What you actually deploy depends on the folder's rendering mode - and for SSR folders it is not everything in the directory. Details ›
Multiple Folders
The point of the whole layout. Three apps, one install, one set of types/helpers:
src/
├── marketing/ MDX, no backend, base "/"
├── app/ React + Hono, base "/app"
└── admin/ Vue + H3, base "/admin"Each has its own kosmo.config.ts, its own routes, middleware, layouts and build. They share one package.json, one node_modules, and one @/ root.
pnpm dev # all folders
pnpm dev app # just one
pnpm build admin # build one, deploy it independently