🍡 mochi

SSR framework for Svelte 5 + Bun with islands-based selective hydration

On this page

Installing mochi-framework puts a mochi-framework binary on your PATH. Inside a project it’s available to package.json scripts directly; anywhere else, run it with bunx:

bunx mochi-framework <command> [options]
bunx mochi-framework --help # list every command
bunx mochi-framework --version # print the installed version

-h/--help and -v/--version work as shorthands.

build

Produces a production bundle by reading config straight from your entry’s Mochi.serve() call, so the prebuilt manifest stays single-sourced with the runtime. This is the command behind your build script:

{
  "scripts": {
    "build": "mochi-framework build"
  }
}
OptionDefaultDescription
--entry <path>./src/index.tsRuntime entry whose Mochi.serve() call supplies routes, markdown, optimize, and publicDir.
--out-dir <path>./.mochiBase build output directory. --dev builds nest under <out-dir>/dev.
--public-dir <path>./publicStatic assets directory. Scanned, never copied — see below.
--asset-prefix <path>/_mochiURL prefix for framework client assets.
--devoffBuild with development: true.

Every SSR entrypoint it compiled is listed as one tree. Your routes come first, then the three groups no route reaches: the error page renders on a throw, an email template on a send, and a server island on a fetch of its own endpoint.

      Route                                   islands   bundle
  ┌ ● /                                             1  2.45 kB
  ├ λ /health                                       -        -
  ├ ● /orders/:id                                   1  2.45 kB

  │   Error page
  ├ ⚠ $mochi/templates/DefaultError.svelte          1  3.17 kB

  │   Email template
  ├ ✉ src/emails/Receipt.svelte                     -    316 B
  ├ ✉ src/emails/Welcome.svelte                     -    288 B

  │   Server island
  ├ ◐ /_mochi/island/Cart_3iqkh56ovhduk             1  89.6 kB
  └ ○ /_mochi/island/Stamp_dnp0teboqefy             0    235 B

  ● page with islands  ·  λ api  ·  ⚠ error page  ·  ✉ email template  ·  ◐ server island with islands  ·  ○ server island

Notes on the three trailing groups:

  • The error page is Mochi’s built-in one until you set errorPage, which is why it reads $mochi/… above.
  • Email templates are found by walking src/emails/ — nothing imports one from a route, so there is no import graph leading there. Their islands column reads - because an island in a template fails the build.
  • Server islands are listed by the endpoint the browser fetches, not by file: the name carries a hash, and two islands can share a source file while keeping separate URLs. The prefix follows --asset-prefix. marks one whose rendered HTML contains hydratable islands of its own — the same thing the islands column counts on every other row, so a mochi:defer mochi:hydrate island reads here and is counted against the page that mounts it.

After the tree, the build lists the resources it emitted — one row per local image import, largest first, so an oversized import is obvious:

      Resource                        dimensions     size
  ┌ ▣ error-page-3jm1noc19vxtj.png       1400×807   330 kB
  └ ▣ debug-bar-ycfe5vg1pwxv.png          1036×72  22.5 kB

  2 assets · 353 kB

Silence the list with Mochi.serve({ build: { resources: false } }) — the build: done summary still reports the asset count.

The manifest stores all artifact paths relative to the out-dir, so the build output is relocatable — move or copy it and boot with outDir (or manifest) pointing at the new location. See Deployment for how the output is served and Relocatable builds for the remaining constraints.

Static files are the exception: the build reads --public-dir only to reject a file that shadows one of your routes, and copies nothing. The runtime serves that directory from disk, so it travels with your source rather than with .mochi/. A 0 public file(s) in the summary for an app that has static assets means --public-dir (or the entry’s publicDir) points somewhere unexpected.

generate-key

Generates a MOCHI_KEY (a base64url-encoded 32-byte secret — the format server-island prop encryption expects) and writes it to .env in the current directory:

bunx mochi-framework generate-key

It creates .env if missing, appends MOCHI_KEY if absent, and prompts before overwriting an existing key.

OptionDescription
-f, --forceOverwrite an existing MOCHI_KEY without prompting.

update-skill

Fetches the latest SKILL.md — agent guidance for coding assistants — and writes it into your project for the given agent (default: claude-code):

bunx mochi-framework update-skill [agent]

Run it again whenever you upgrade the framework to keep the guidance in sync. The optional agent argument controls the destination; see Docs for LLMs for the full list of supported agents.