🍡 mochi
An experimental SSR framework for Svelte 5 and Bun.
Render everything on the server; ship JavaScript only where it earns its place.
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
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
Hello World
The simplest possible Mochi page — pure server-rendered Svelte.Hydration Modes
The same component rendered five ways — eager, lazy, visible, rootMargin-tuned, and deferred server island.View Transitions
Drop <ViewTransitions /> into a shared layout to animate full-page navigations with zero JavaScript.Custom Transitions
Bring your own @keyframes to <ViewTransitions /> via custom={{ in, out }} — here, a funky 3D spin.Shared State
Two separate islands sharing the same reactive $state.Server Islands
Components marked mochi:defer render server-side on demand after the initial page is delivered.Crossing the server-client boundary with props
How props travel from a server-rendered parent into a hydrated island — Date, Map, Set, BigInt, URL, typed arrays, and even cyclic refs survive devalue’s round-trip.Client-only Islands
Components marked mochi:clientOnly skip SSR entirely and mount in the browser — a fallback snippet fills in until then.Lazy Islands
Islands marked mochi:hydrate:visible hydrate and load their CSS only when scrolled into view.Lazy Server Islands
Server islands marked mochi:defer:visible only fetch when the wrapper scrolls into view.Font loading
Ship fonts via @fontsource packages or standalone .woff2 files — automatically bundled and linked from the page head.MdSvex
A .md file compiled through mdsvex and rendered as a Svelte component, with an embedded <script> block.Nested Components
A five-level recursive tree — hydrating the root carries the whole subtree in one island.Nested Islands
Islands inside islands — a mochi:defer server island wrapping mochi:hydrate components, and a server island nesting both a deferred and a deferred-hydrated server island.Nested Island Max Depth
A chain of mochi:defer server islands nested four levels deep — each fetches the next on demand, and the prebuild precompiles the whole chain.Shared Props
Nine islands, three unique payloads — each set serialized once and referenced via props-ref.Unique IDs
Svelte's native $props.id() inside islands — SSR-consistent, unique per instance, namespaced in server islands.HTML Entities in Props
HTML entities in a static island prop (label="Tom & Jerry") decode to their characters — identical on the server and after hydration.Data & serialization
Server Props
Define serverProps on Mochi.page() to pass fresh data into a Svelte page on every request.Data Loading
Server-side fetch from PokéAPI cached via MochiCache and rendered at request time.Hydratable
Compute a value once on the server with hydratable(); the hydrated island reads it from <head> instead of re-running the async work.Cookies
Read and write cookies on the server and the client through one MochiCookieJar API.Isomorphic URL
One import for the current URL — reads from the request on the server, window.location on the client.Cache Events
Subscribe to MochiCache lifecycle events through mochiEvents and log them to the server console.Image Resizing
On-the-fly image resizing on Bun.Image, served from an encrypted, stale-while-revalidate disk cache.Image Events
Subscribe to image:store / image:delete on mochiEvents to mirror the <Image> cache to durable storage like S3.Advanced Image use
Decode, resize, rotate, flip, modulate, and re-encode with the raw Bun.Image pipeline — every option, server-rendered to inline data URLs.Request ID
Every request gets a UUID v7 — read it server-side via getRequestContext().requestId; the same id rides every lifecycle event for correlation.Cookie Vary Test
A page that sets Vary: Cookie on its response — useful for testing cookie-partitioned cache keys.Endpoints & realtime
Real-time Chat
A hydrated island over a Mochi.ws() route, with pub/sub broadcast and in-memory history.API Endpoints
JSON routes defined with Mochi.api(), tested live against the running server.File Routes
Serve a file from disk with Mochi.file() — static path or a per-request resolver.Real-time Streams
WebSocket and SSE clocks, lazily hydrated via mochi:hydrate:visible.Background jobs with queues
Offload work to a Mochi.queue() with an embedded worker — no Redis.Forms
Form Actions
A login form rendered twice — plain HTML POST and intercepted with {@attach enhance(...)}.Using form return data
An action returns data via success({...}); {@attach enhance(...)} updates the UI in place, plain HTML re-renders the page.Form Errors
A thrown action error shown inline via {@attach enhance(...)}, or as the Mochi error page on plain submit.Form Redirects
redirect(303, …) intercepted as a JSON envelope by {@attach enhance(...)}, or followed natively by the browser.File Uploads via form actions
multipart/form-data submission, validated with fail() and success(), shown enhanced and plain.Reloading associated form data
After a successful submit, refetch the related list inside enhance() — or rely on the post-POST re-render.Cancelling form submissions
cancel() prevents the fetch from firing; controller.abort() stops one mid-flight.