Documentation
What gets generated
Every file create-stoneware writes, what it is for, and what a build adds.
A new project is twelve files. There is no hidden state, no lockfile-adjacent cache to understand, and nothing generated that you are not meant to read.
my-site/ │ ├── routes/ Server-only. Never ships JavaScript. │ ├── index.tsx A page. Maps to "/" │ └── _404.tsx Shown for any path that does not match. │ Leading _ means it is not itself a page. │ ├── islands/ The only place client JS originates. │ └── Counter.tsx Hydrates on load. Gets its own bundle. │ ├── lib/ Behavior functions and shared utilities. │ Ships JS only if an island imports it. │ ├── public/ Served as-is, at the URL root. │ └── styles.css -> GET /styles.css │ ├── stoneware.config.ts Port, CSP override, CSRF settings. ├── tsconfig.json jsx: "react-jsx", jsxImportSource: "stoneware" ├── package.json scripts: dev / build / start │ ├── .env STONEWARE_CSRF_SECRET, generated unique. Gitignored. ├── .env.example Tracked template, no value. └── .gitignore node_modules/ .stoneware/ .env
The two directories that matter
routes/ and islands/ are not a style preference. They are the mechanism behind the framework's central claim, and the difference is enforced by the build rather than by convention.
routes/** islands/**
│ │
│ └──► entry point for Bun.build (browser target)
│ │
│ └──► shipped to the client
│
└──► never passed to the bundler at all
│
└──► cannot reach the client, by constructionNothing scans your route files for a directive and decides. Server-only code is not excluded by a heuristic that might get it wrong — it is simply never handed to the bundler.
What a build adds
stoneware build writes everything into .stoneware/, which is gitignored. Deleting it is always safe.
my-site/.stoneware/
│
├── server.js One bundle: framework + every route + every island.
│ Routes are inlined, so no transpiling per request.
│
├── islands.json Island name -> public chunk URL.
│ { "Counter": "/_stoneware/Counter-jzp1gax8.js" }
│
├── static/ Served under /_stoneware/*, immutable (content-hashed).
│ ├── Counter-jzp1gax8.js One entry chunk per island.
│ ├── chunk-gcapcpwn.js Shared runtime: signals + hydrate.
│ └── styles-4kq2n7wd.css Every .css found beside your code.
│
└── entries/ Generated island entry points. Build input.routes/ must still exist at runtime. Route modules are inlined into server.js, but path matching reads the directory for its filenames — never for its contents.
The shared chunk is why a page with three islands does not download signals three times. Each island entry is small; the runtime they have in common is hoisted out once.
The stylesheet only appears if the project has a .css file under routes/, islands/ or lib/. A new project styles itself from public/styles.css instead, at a fixed URL — both work, and the co-located route is the one that scales past a single file.
Something wrong in the framework itself rather than the page? Open an issue on GitHub.