SSR framework for Svelte 5 + Bun with islands-based selective hydration
On this page
Server-only imports
Any module reachable from a hydratable island gets bundled into the client. To use a server-only library (bun:sqlite, node:fs, anything that touches the filesystem) from inside an island, put the library plus a thin wrapper in a *.server.ts file. Mochi replaces these files with throwing-Proxy stubs on the client; the real module is only compiled for SSR.
// db.server.ts
import { Database } from 'bun:sqlite';
const db = new Database(':memory:');
export const getVersion = (): string => (db.query('SELECT sqlite_version() as v').get() as { v: string }).v;<!-- FactCard.svelte — hydratable island -->
<script lang="ts">
import { hydratable } from 'svelte';
import { getVersion } from './db.server.ts';
const version = await hydratable('app:sqlite-version', () => getVersion());
</script>
<p>SQLite {version}</p>The .server.ts (or .server.js) suffix is the entire convention — no runtime API to call, no config. Import with the extension (./db.server.ts); extensionless ./db.server also works. Types follow the import normally.
What gets stubbed
Every named and default export of a .server.ts file is replaced with a Proxy that throws on both function calls and property access. The error message names the export and its origin file so a stray client-side use surfaces cleanly:
getVersion from /…/db.server.ts was called on the client; this is a server-only export.Not supported
export * from './x'— Mochi warns at build time; declare named exports in the.server.tsfile directly..server.svelte— component-level convention is not provided. Put server-only code in plain TS and call it from a component.