🍡 mochi

🍡 mochi

An experimental SSR framework for Svelte 5 and Bun.

Render everything on the server; ship JavaScript only where it earns its place.

Watch the Mochi demo on This Week in Svelte

What is Mochi?

Mochi is a lightweight, server-first framework for Svelte 5 on Bun. Mochi websites render server-side on every request and ship as plain HTML. Components only ship JavaScript when you explicitly mark them as islands.

Quick start

Requires Bun >= 1.3.14. Why Bun? Supports macOS Supports Windows Supports Linux

Documentation

Setup, hydration modes, routes, hooks, forms, cookies — everything in one place.

Start reading →

Demos

Each demo lives on its own page. Pick one below to see the feature in isolation.

Basic

How server-side rendering works — a Mochi.page() renders Svelte on the server and ships zero JavaScript. How the hydration modes work — mochi:hydrate, mochi:hydrate:visible, rootMargin tuning, and mochi:defer server islands side by side. How hydratable() works — compute a value once on the server and reuse it on the client instead of re-running async work during hydration. How isHydratable() works — detect from any depth whether the current subtree will hydrate, for SSR-only fallbacks without prop forwarding. How view transitions work — drop <ViewTransitions /> into a layout to animate full-page navigations with zero JavaScript. How custom view transitions work — supply your own @keyframes to <ViewTransitions /> via custom={{ in, out }}. How shared state across islands works — two separate islands driving the same reactive $state. How server islands work — components marked mochi:defer render server-side on demand after the initial page is delivered. How props cross the server-client boundary — Date, Map, Set, BigInt, URL, typed arrays, and even cyclic refs survive devalue's round-trip into a hydrated island. How client-only islands work — components marked mochi:clientOnly skip SSR and mount in the browser, with a fallback snippet until then. How lazy hydration works — islands marked mochi:hydrate:visible hydrate and load their CSS only when scrolled into view. How lazy server islands work — server islands marked mochi:defer:visible only fetch when the wrapper scrolls into view. How font loading works — ship fonts via @fontsource packages or standalone .woff2 files, automatically bundled and linked from the page head. How mdsvex works — a .md file compiled through mdsvex and rendered as a Svelte component, embedded <script> and all. How whole-subtree hydration works — a five-level recursive tree where hydrating the root carries the entire subtree in one island. How nested islands work — a mochi:defer server island wrapping mochi:hydrate components, and server islands nesting more server islands. How deeply nested server islands work — a four-level mochi:defer chain where each level fetches the next on demand and the prebuild precompiles the whole chain. How island prop deduplication works — nine islands share three unique payloads, each serialized once and referenced via props-ref. How stable island IDs work — Svelte's native $props.id() gives SSR-consistent, per-instance ids, namespaced inside server islands. How HTML entities in island props work — an entity in a static prop (label="Tom &amp; Jerry") decodes identically on the server and after hydration. How third-party Svelte 5 libraries run in islands — Runed's reactive utilities (Debounced, StateHistory, PersistedState, PressedKeys, AnimationFrames, FiniteStateMachine, resource…) hydrated inside Mochi.

Data & serialization

How server props work — pass fresh per-request data into a page via serverProps on Mochi.page(). How server-side data loading works — fetch on the server, cache with MochiCache, and render at request time. How cookies work — read and write on the server and the client through one MochiCookieJar API (cookies.get/set/delete). How the isomorphic URL helper works — one import that reads the request URL on the server and window.location on the client. How cache events work — subscribe to MochiCache lifecycle events (hit, miss, set, evict) through mochiEvents for observability. How the request cache works — requestMemo and requestCache run an expensive computation once per request no matter how many components call it, then discard it at the request boundary. How the <Image> component works — named sizes, ThumbHash blur-up placeholders, and encrypted deferred URLs whose transforms run lazily on the endpoint. How image invalidation works — invalidateImage() hard-evicts the shared original so every named size re-fetches in lockstep. How the image transform pipeline works — declare resize / rotate / flip / modulate / format as named sizes; getImageUrl mints a deferred URL, getImage runs one inline. How image events work — subscribe to image:store / image:delete on mochiEvents to mirror the <Image> cache to durable storage like S3. How request IDs work — every request gets a UUID v7 on getRequestContext().requestId that rides every lifecycle event for correlation. How cookie-partitioned caching works — a page that sets Vary: Cookie so responses key on cookies. How Portable Text rendering works — @portabletext/svelte maps a JSON block array onto your own Svelte components for types, marks, block styles and lists.

Endpoints & realtime

Forms

Errors

Demo sites