---
title: 'Production builds'
slug: production-builds
description: 'How a Mochi build behaves in production: relocatable output, sub-path hosting, and a persistent image cache.'
---
# Production builds
`mochi-framework build` writes a self-contained build to `.mochi/` (see the [CLI reference](/docs/cli/)). 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](/docs/deployment-options/); to containerize it, see [Building a Dockerfile](/docs/docker/).
## 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:
```ts
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:
```ts
Mochi.serve({ manifest: '/srv/app/build/manifest.json' });
```
Five things still anchor a prebuilt app to its project:
- **Build and serve from the same working directory.** Components are keyed relative to the project root, which both `mochi-framework build` and `Mochi.serve()` take to be the current working directory. Run both from the project root.
- **Ship your `public/` directory.** Static files are never copied into the build. The runtime scans `publicDir` (default `./public`) at startup in production exactly as in development. A deploy that ships only `.mochi/` and `src/` 404s every static file.
- **Keep the out-dir in the project tree.** The compiled SSR modules resolve `node_modules` from the out-dir's location.
- **On-demand server islands need sources.** Islands missing from the manifest are compiled at request time from source paths recorded at build. Prebuilt islands relocate fine.
- **Keep email templates in `src/emails/`.** A `Mochi.email({ component })` template is reachable only at send time, so the build walks that directory to find it. See [Svelte templates](/docs/email/#svelte-templates).
The manifest records a schema version, and the runtime loads only the exact version it writes. Booting a build made by a different `mochi-framework` version throws at startup. Always run `mochi-framework build` with the same version you serve with.
## 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:
```sh
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.
`assetPrefix` covers framework assets only. Links you write yourself (`href="/about"`) and files in `public/` are served at the paths you give them, so prefix those in your own markup.
The same prefix is what makes a hand-rolled static export work: copy `.mochi/svelte-client` to `/my-app/_mochi/client` and `.mochi/svelte-css` to `/my-app/_mochi/css`, save each prerendered page, and nothing needs rewriting.
### Rewriting URLs yourself
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 `` on that page does.
## Persistent image cache
Mochi's [image cache](/docs/images/) 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:
```ts
Mochi.serve({
image: { cacheDir: process.env.MOCHI_IMAGE_CACHE_DIR /* , sizes: … */ },
});
```
```yaml
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.
Keep `MOCHI_KEY` stable across restarts. Image URLs are signed with a key derived from it, so a changed key invalidates already-minted links even though the cached bytes are still on disk.