Documentation
What gets generated
Every file create-stoneware writes, what it is for, and what a build adds.
A new project is sixteen 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. │ ├── robots.txt.ts A route that returns text rather than HTML, │ │ so it is written at its literal path. │ └── sitemap.xml.ts Built with sitemap(). Add the pages you │ publish to the list it exports. │ ├── islands/ The only place client JS originates. │ └── Counter.tsx Hydrates on load. Gets its own bundle. │ ├── lib/ Behavior functions and shared utilities. │ └── site.ts SITE_URL, for canonical links and the sitemap. │ Ships JS only if an island imports it. │ ├── public/ Served as-is, at the URL root. │ ├── styles.css -> GET /styles.css │ ├── favicon.ico -> GET /favicon.ico │ └── mark.svg -> GET /mark.svg │ ├── stoneware.config.ts Port, CSP override, CSRF settings. ├── tsconfig.json jsx: "react-jsx", jsxImportSource: "stoneware" ├── package.json scripts: dev / build / start ├── README.md The commands, and where the docs live. │ ├── .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,
│ plus the pattern table used to match paths.
├── server.js.map Source map, so a stack trace still names your file.
├── server-entry.ts The generated entry the bundle was built from.
│
├── islands.json Island name -> public chunk URL.
│ { "Counter": "/_stoneware/Counter-rzesgezg.js",
│ "@runtime": "/_stoneware/stoneware-runtime-2pweh7fr.js" }
│
├── static/ Served under /_stoneware/*, immutable (content-hashed).
│ ├── Counter-rzesgezg.js One entry chunk per island.
│ ├── chunk-y24dm8pf.js Shared code, hoisted out of the entries.
│ ├── stoneware-runtime-2pw...js Lazy hydration. Always emitted; a few hundred bytes.
│ └── styles-4kq2n7wd.css Every .css found beside your code.
│
└── entries/ Generated island entry points. Build input.Since 0.1.4 the source tree is a build input, not a runtime dependency. Routes and islands are inlined into server.js and paths are matched against a pattern table written beside them, so a built server runs with routes/ and islands/ deleted — which is what makes a minimal container image possible.
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.