stoneware

Bun-native · server-first · v0.1.6

Shape your web application at build/server time

HTML by default. JavaScript by choice.

Build content-heavy sites without shipping a client runtime to pages that do not need one. Interactivity is opt-in per directory, and the safe path is the default one.

Client runtime
3.2 KB
One island
0.2 KB
Page with no islands
0 B
Runtime deps
1

This site is the documentation for stoneware-dev/stoneware-core — the framework's own source, issues, and releases live there.

Plain clayBisqueGlazeVitrified

The problem

The web ships JavaScript to sites that are documents

Stoneware inverts the default: HTML first, JavaScript only where you ask for it. Four specific problems that follow from it.

You ship a runtime to render a document
A page with no islands ships zero bytes — no runtime, no hydration shim, no script tag. Asserted by the test suite, not just intended.
The client/server boundary drifts
The boundary is a directory, enforced by the build. Files under routes/ are never handed to the bundler, so they cannot reach the client.
You hydrate things that will never change
The header, footer and article have nothing to hydrate. They are strings the server produced, and they stay that way.
Security is opt-in, and CSP is what everyone skips
Stoneware never emits inline executable script, so script-src 'self' just works. This site runs under the default policy, unmodified.

All seven, and the ones it deliberately does not solve →

Install

One command, either runner

Scaffolding runs on plain Node, so it works before Bun is installed. Everything after that runs on Bun.

bunx create-stoneware my-site

Both runners scaffold the project. Stoneware itself runs on Bun: the generated app needs it for stoneware dev and stoneware build.

Principles

Six decisions, held consistently

Each of these is a constraint the framework enforces rather than a convention it suggests.

01

Server-first

Every route renders to a complete HTML string. A page with no islands ships zero bytes of JavaScript — not a small runtime, not a hydration shim, nothing.

02

No component model

Templates are plain functions: props in, markup out. No classes, no hooks, no lifecycle. Logic lives in ordinary functions, not inside UI definitions.

03

Signals, not an engine

Islands use Preact Signals directly. Stoneware does not implement a reactive graph — that is a deliberate scope boundary, not an oversight.

04

Interactivity by location

A file under islands/ hydrates. A file under routes/ never does. No per-file directive to remember, and no way to make a page interactive by accident.

05

Safe before configured

Auto-escaping, automatic CSRF verification, and a restrictive CSP need no configuration to be on. The unsafe path requires typing more.

06

Bun's own APIs

Bun.serve, Bun.build, Bun.escapeHTML, Bun.CSRF, Bun.FileSystemRouter. No npm package reimplementing something the runtime already ships.

Islands

Interactivity you opt into

islands/Counter.tsxtsx
// islands/Counter.tsx — the only file here that ships JS
import { signal } from "stoneware/signals";

const count = signal(0);

export default function Counter() {
  return (
    <button onClick={() => count.value++}>
      Clicked {count} times
    </button>
  );
}

Live, on this page

Server-rendered as static HTML, then hydrated. Clicking updates one text node — the tree is never re-run and nothing is diffed.

Documentation

Read on

What it solves

The specific problems Stoneware exists for — and the ones it does not.

Quick start

Scaffold a project, run it, and understand what the two directories mean.

What gets generated

Every file create-stoneware writes, what it is for, and what a build adds.

How it works

The path a request takes, and what hydration does to the DOM.

Routing

File-based, Next.js-style conventions resolved by Bun's own router.

Islands

How a component earns its JavaScript, and what hydration actually does.

When islands hydrate

client:visible, client:idle and client:media — and what a page stops downloading.

Head and images

Per-page metadata, and an <Image> that fixes layout shift without a build pipeline.

SEO and sharing

One seo() call for search engines, every social network, and rich results.

Error pages

Custom 404 and 500 pages, and the three properties that hold whether or not you write them.

Error boundaries

Lose one subtree instead of the whole page, without losing the error.

Styling

Co-located CSS, collected by the build, with no import and no link tag to maintain.

Server actions

Form handling where CSRF verification is structural, not a decorator.

Security defaults

What is on before you configure anything, and why it cannot be off by accident.

CLI and builds

Dev server, production build, and what each command actually emits.

Deploying

What a host has to provide, which platforms qualify, and the one file you add.

What's new

0.1.6 — error boundaries, a request hook, a request path about three times faster, and a dev server that stops breaking itself.

v0.1.4 & v0.1.5

The two deploy releases — why a build would not run where it was not built.

Past releases

What shipped in 0.1.3 and 0.1.2, and what each change replaced.

Databases

Where db.ts goes, where queries run, and the one place they must never.

Middleware and APIs

One file that runs on every request, and what changed for API routes.

Benchmark

The same 16-page site built in Stoneware, Astro and Next.js, measured.