🍡 mochi

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

On this page

Production builds

mochi-framework build writes a self-contained build to .mochi/ (see the CLI reference). This page covers what that build gives you once it is deployed: it relocates cleanly from where you built it to where you run it, it can be hosted under a sub-path, and its image cache can survive container restarts. For where to run it, see Deployment options; to containerize it, see Building a Dockerfile.

Relocatable builds

A manifest holds no absolute paths — artifacts are written relative to the out-dir, sources relative to the project root — so you can build in one place and run in another. Build in a CI stage, copy .mochi/ into the final image, and point the runtime at wherever it landed:

Mochi.serve({ outDir: './.mochi' }); // default — or wherever you copied it

Paths resolve against the manifest’s own directory, so pointing manifest at a relocated build works on its own:

Mochi.serve({ manifest: '/srv/app/build/manifest.json' });

Sub-path and static hosting

Mochi writes absolute URLs for everything it owns: /_mochi/client/…, /_mochi/css/…, the island component-url, and the import specifiers inside the client chunks. To host under a sub-path such as https://example.com/my-app/, bake the prefix in at build time:

mochi-framework build --asset-prefix /my-app/_mochi

The value lands in manifest.json and every emitted URL carries it. Mochi.serve({ assetPrefix }) sets the same thing for on-demand compilation, but once a manifest exists its value wins and a differing serve() value only logs a warning.

The same prefix is what makes a hand-rolled static export work: copy .mochi/svelte-client to <host>/my-app/_mochi/client and .mochi/svelte-css to <host>/my-app/_mochi/css, save each prerendered page, and nothing needs rewriting.

Rewriting URLs yourself

Since: 0.10.0 (not released yet): Before 0.10.0 a relative component-url resolved against the island loader module (/_mochi/client/), not the page.

If you post-process the HTML into page-relative URLs instead, treat component-url like any other attribute: it resolves against the page, so ../_mochi/client/… on /my-app/writing/ loads from /my-app/_mochi/client/, the same place a <link href="../_mochi/css/…"> on that page does.

Persistent image cache

Mochi’s image cache is written to disk under cacheDir (default ./.mochi/image-cache). In a container that directory is recreated on every restart, so each redeploy starts with a cold cache and re-fetches and re-transforms every image. To keep the transformed bytes across restarts, point cacheDir at a dedicated path and mount a volume there:

Mochi.serve({
  image: { cacheDir: process.env.MOCHI_IMAGE_CACHE_DIR /* , sizes: … */ },
});
services:
  site:
    image: your-app
    environment:
      MOCHI_IMAGE_CACHE_DIR: /data/image-cache
    volumes:
      - image-cache:/data/image-cache

volumes:
  image-cache:

A plain docker run -v image-cache:/data/image-cache your-app mounts the same volume. Keep the mount off ./.mochi — its build cache is rebuilt on every boot and must not persist. If the container runs as a non-root user, pre-create the directory owned by that user in your Dockerfile so the mounted volume is writable.