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, previewPort, scripts
├── tsconfig.json -> minimal config, add paths to `include` to have them typechecked
│
├── src/ ✍️ YOU WRITE THIS
│ ├── front/ -> one source folder
│ └── admin/ -> another, fully independent
│
├── lib/ 🤖 DERIVED - never edit
│ ├── tsconfig.json -> base config the root tsconfig extends
│ ├── front/ -> derived code for src/front
│ └── admin/ -> derived code for src/admin
│
├── dist/ 📦 BUILD OUTPUT
│ ├── run.js -> runs every folder, one process
│ ├── 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 -> type-safe navigation component
│
└── 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-frontend 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 derived code is not committed - it is a build artifact. What is committed is the per-route derivation 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 rebuild, 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>/ | derived 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/ -> hashed, served at <base>/assets/
└── public/ -> copy of public/, served at <base>/The simplest way to run all of this is node dist/run.js, which dispatches across every folder in one process. Details ›
Folders can also be deployed separately, in which case what you 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