---
title: 'Welcome'
slug: intro
description: 'A lightweight, server-first Svelte 5 framework running on Bun that ships client-side JavaScript only for interactive islands.'
---
# mochi
Mochi is a lightweight, server-first framework for [Svelte 5](https://svelte.dev/) on [Bun](https://bun.sh/). Mochi websites render server-side on every request and ship as plain HTML. Components only ship JavaScript when you explicitly mark them as islands.
## Server-rendered, with island interactivity
The websites we visit on the web are mostly static — text, images and links. Only a handful of elements on any given page actually need to be interactive: a search box, a logged-in badge, a comments widget. Mochi reflects this at the core of its design. Mochi sites renders server-side as plain HTML; the interactive pieces are marked with the `mochi:hydrate` directive and ship JS as interactive islands embedded in that HTML.
Go ahead, try hydrating the page below and see which components will load JavaScript.
The header text, the main column, and the footer ship as HTML and stay that way. The badge and the sidebar nav are wrapped as islands — same SSR HTML on first paint, with JS attached on top. Everything else is zero-JS forever.
## Why would I consider Mochi over SvelteKit?
- **Faster sites.** Mochi ships zero client JavaScript by default. SvelteKit code-splits per route but still hydrates the entire page — even purely static content. Mochi only hydrates the components you explicitly mark as islands, which means less JS on first load, better bfcache behavior, and a natural fit for any site.
- **Performant hydration.** Avoid hydrating islands until the users scrolls into them with `mochi:hydrate:visible`. Or avoid hydrating at all if the user never scrolls down to that component. Your users will thank you for the faster experience.
- **Uses the platform.** Mochi ships with first-class support for View Transitions. No client side router, no state to keep track of between requests.
- **No heavy bundler (no Vite).** Uses the lighting-fast Bun bundler, which builds sites with hundreds of routes in seconds.
- **Real-time built in.** WebSockets and Server Sent Events are first-class route types — no extra packages or services required.
**Mochi is in early development.** Only use in production if you are brave.
## Community
Questions, bug reports, ideas, or just want to see what others are building? Join the [Mochi Discord](/discord/).
---
title: 'Your first Mochi app'
slug: your-first-mochi-app
description: 'Build your first page with serverProps, selective hydration, and server islands in four steps.'
---
## Your first Mochi app
Let's build a small app that exercises every server/client boundary you'll touch in real code. We'll put together a single `/hello` page in four steps, picking up one pillar at a time: [`serverProps`](/docs/defining-routes/) for loading data on every request, and [passing props to islands](/docs/island-props/) so a server-rendered parent can hand values to a hydrated child. Then we'll add [`mochi:hydrate`](/docs/selective-hydration/) for one interactive island while the rest stays zero-JS, and [`mochi:defer`](/docs/server-islands/) for a server island that renders separately from the main request.
By the end we'll have a greeting card with a live like button and a personalized welcome that loads in after the page renders.
### Set up
You'll need [Bun installed](https://bun.com/docs/installation) (>=1.3.13). Scaffold a new project with the official CLI and pick the **minimal** template when prompted:
```sh
bun create mochi@latest my-app
# choose: minimal
cd my-app
bun install
bun run dev
```
The scaffold gives you a working app on `http://localhost:3333`. Its entry point is `src/index.ts`, which boots the server and declares your routes inline in the `Mochi.serve()` call:
```ts
// file: src/index.ts (scaffolded)
import { Mochi } from 'mochi-framework';
const PORT = Number(process.env.PORT) || 3333;
await Mochi.serve({
port: PORT,
development: process.env.MODE === 'development',
routes: {
'/': Mochi.page('./src/HelloWorld.svelte'),
},
});
```
`src/index.ts` is the single bootstrap file — you'll edit its `routes` object next, then build the Svelte components it points at.
### Step 1 — Register the route
Now let's point `/hello` at a Svelte page and give it some data to render. Open `src/index.ts` and replace the scaffolded route inside `routes` with this one. `serverProps` is either a plain object or a `(req, params) => props` resolver — whatever it returns becomes the page component's `$props`.
```ts
// file: src/index.ts
import { Mochi } from 'mochi-framework';
await Mochi.serve({
port: Number(process.env.PORT) || 3333,
development: process.env.MODE === 'development',
routes: {
'/hello': Mochi.page('./src/Hello.svelte', {
serverProps: () => ({
siteName: 'Mochi',
renderedAt: new Date().toISOString(),
}),
}),
},
});
```
The resolver runs on every request, so each reload produces a fresh `renderedAt`. See [Defining routes](/docs/defining-routes/) for the full `serverProps` contract and the other `Mochi.*` route helpers. (The scaffolded `src/HelloWorld.svelte` is now unused — feel free to delete it.)
### Step 2 — The page component
Next, let's write the page itself. `Hello.svelte` stays server-only (all `Mochi.page()` entry components are server-only) — it consumes the `serverProps`, renders a static layout, and mounts the two child islands we'll build next. Notice that even though it imports two components that ship JavaScript, this file itself ships zero: the `mochi:` directives where we render the components decide what hydrates.
```svelte
Welcome to {siteName}
Rendered at {renderedAt}
Loading…
```
The `initialLikes={42}` value crosses the server→client boundary. Mochi serializes island props with [`devalue`](/docs/island-props/), so `Date`, `Map`, `Set`, `BigInt`, and cyclic references all survive the trip — not just JSON-safe values.
Island props end up serialized into the HTML payload, so they're **visible to the client**. Never pass secrets, API keys, or session tokens this way.
### Step 3 — A hydrated island
Now let's give the user something to click! `LikeButton.svelte` is a normal Svelte 5 component — we accept `initialLikes` as a prop, keep a `$state` counter, and bump it on click.
```svelte
```
The `mochi:hydrate` directive lives **where we render the component** in `Hello.svelte`, not inside the island itself. The same component can be mounted statically elsewhere.
Reload the page in dev mode and you'll see Mochi's [debug bar](/docs/debug-bar/) pinned to the bottom-right of the page. Open the **Islands** panel — `LikeButton` shows up tagged `mochi:hydrate` with the byte size of its serialized props (the `initialLikes` value), and the crosshair icon next to each row scrolls to and outlines the island on the page.
### Step 4 — A server island
Finally, let's add a personalized greeting that doesn't block the rest of the page. We marked `Visitor.svelte` with `mochi:defer` back in Step 2, so it skips the initial SSR pass — the page ships with our `
Loading…
` fallback in its place. The browser then fetches the component _in a separate request_, the server renders it, and the result swaps in.
The deferred island fetch is its own request, so `getRequestContext()` inside the island sees the island URL — not the page URL. Read page-specific state in the parent and forward it as a prop.
Update `Hello.svelte` to read `?name=` and pass it through to `Visitor`:
```svelte
Welcome to {siteName}
Rendered at {renderedAt}
Loading…
```
```svelte
Welcome back, {name}!
```
`mochi:defer` lets the call site pass fallback children (our `
Loading…
`) that render in place of the island until the deferred fetch resolves. The framework handles the swap — `Visitor` itself never renders `children`, but we declare it in the prop type so TypeScript accepts the fallback at the call site.
The `name` prop rides through the same `devalue` round-trip as `initialLikes`. Try [`/docs/your-first-mochi-app/hello?name=Alice`](/docs/your-first-mochi-app/hello?name=Alice) — the main page is identical for every visitor, but the deferred fragment swaps in a personalized greeting.
Cookies are an exception worth knowing: the browser sends them along with the island fetch automatically, so `getRequestContext().cookies` inside a server island reads the visitor's cookies without needing the parent to forward them.
### See it live
The finished app is running on this site at [**/docs/your-first-mochi-app/hello**](/docs/your-first-mochi-app/hello). Click the heart, then try [`/docs/your-first-mochi-app/hello?name=Alice`](/docs/your-first-mochi-app/hello?name=Alice) to watch the deferred fragment swap in a personalized greeting. The [debug bar](/docs/debug-bar/)'s **Islands** panel groups the two islands separately: `LikeButton` under hydrated islands as `mochi:hydrate`, and `Visitor` under server islands as `mochi:defer` with a lock icon (server-island props are HMAC-signed before being sent to the client).
### What's next
- [Defining routes](/docs/defining-routes/) — `Mochi.page`, `Mochi.api`, `Mochi.ws`, `Mochi.sse`, and the full `serverProps` contract
- [Selective hydration](/docs/selective-hydration/) — `mochi:hydrate`, `isHydratable`, `$props.id()`
- [Lazy hydration](/docs/lazy-hydration/) — `mochi:hydrate:visible` for below-the-fold islands
- [Server islands](/docs/server-islands/) — `mochi:defer`, signed props, and `MOCHI_KEY`
- [Passing props to islands](/docs/island-props/) — every type `devalue` can round-trip
---
title: 'Coming from SvelteKit'
slug: coming-from-sveltekit
description: 'A mapping of SvelteKit concepts to their Mochi equivalents for developers switching frameworks.'
---
## Coming from SvelteKit
You have likely already been using SvelteKit as your main framework for Svelte. Here is a quick list of most of the SvelteKit features and how they map to equivalent concepts in Mochi, so you can be up and running quickly.
### Routing
SvelteKit's file-based router (`src/routes/foo/+page.svelte`) is replaced by a programmatic routes record passed to `Mochi.serve({ routes })`. Each pattern is a Bun router string; each value is built with `Mochi.page`, `Mochi.api`, `Mochi.ws`, or `Mochi.sse`.
```
// SvelteKit
src/routes/+page.svelte → /
src/routes/posts/[slug]/+page.svelte → /posts/:slug
src/routes/health/+server.ts → /health
```
```ts
// file (Mochi): src/index.ts
import { Mochi } from 'mochi-framework';
await Mochi.serve({
routes: {
'/': Mochi.page('./src/Home.svelte'),
'/posts/:slug': Mochi.page('./src/Post.svelte'),
'/health': Mochi.api(() => Response.json({ status: 'ok' })),
},
});
```
### Advanced routing
Mochi uses Bun's router patterns — `:slug` for required params, `*` for catch-all. There is no SvelteKit-style `[[optional]]` segment, no `[param=matcher]` syntax, and no `src/params/` directory. Validate the shape of a parameter inline.
```ts
// file (SvelteKit): src/params/fruit.ts
export function match(param: string): param is 'apple' | 'orange' {
return param === 'apple' || param === 'orange';
}
// then: src/routes/fruits/[name=fruit]/+page.svelte
```
```ts
// file (Mochi): src/index.ts
import { Mochi, error } from 'mochi-framework';
await Mochi.serve({
routes: {
'/fruits/:name': Mochi.page('./src/Fruit.svelte', {
serverProps: ({ params }) => {
if (params.name !== 'apple' && params.name !== 'orange') error(404, 'Unknown fruit');
return { name: params.name };
},
}),
'/files/*': Mochi.page('./src/Files.svelte'),
},
});
```
Register the most specific patterns first — Bun matches in declaration order, not by file-name sort.
### Layouts
SvelteKit's `+layout.svelte` / `+layout.server.ts` have no Mochi equivalent. In SvelteKit, layouts persist across navigations — the client-side router keeps the layout component mounted and only swaps the page slot, which also lets `+layout.server.ts` skip refetching data that hasn't been invalidated. Mochi has no client-side router, so every navigation is a full page load; there is no component tree to persist and no data to selectively revalidate.
Instead, create a wrapper component that accepts `children`, and import it from each page.
```svelte
{@render children()}
```
```svelte
{@render children()}
```
In SvelteKit, `+layout.svelte` wraps `+page.svelte` automatically. In Mochi, the page must import and wrap itself:
```svelte
Posts
{#each posts as post}
{post.title}
{/each}
```
Share data that SvelteKit's `+layout.server.ts` would have loaded via a common helper, and spread the result into each route's `serverProps`:
```ts
// file (SvelteKit): src/routes/+layout.server.ts
export async function load() {
return { user: await loadCurrentUser() };
}
```
```ts
// file (Mochi): src/lib/baseProps.ts
export async function baseProps() {
return { user: await loadCurrentUser() };
}
```
```ts
// file (Mochi): src/index.ts
import { Mochi } from 'mochi-framework';
import { baseProps } from './lib/baseProps';
await Mochi.serve({
routes: {
'/': Mochi.page('./src/Home.svelte', {
serverProps: async () => ({ ...(await baseProps()), posts: await loadPosts() }),
}),
},
});
```
Every page that shares a shell imports the layout component explicitly — there is no automatic nesting. This is more verbose than SvelteKit, but makes the component tree visible at each call site.
### Load functions
SvelteKit's `load` export becomes `serverProps` on your `Mochi.page` call. It's either a plain object or a `(req, params) => props` resolver (sync or async); the result is passed to the component as `$props`.
```ts
// file (SvelteKit): src/routes/posts/[slug]/+page.server.ts
export async function load({ params }) {
return { post: await loadPost(params.slug) };
}
```
```ts
// file (Mochi): src/index.ts
import { Mochi } from 'mochi-framework';
await Mochi.serve({
routes: {
'/posts/:slug': Mochi.page('./src/Post.svelte', {
serverProps: async (_req, params) => ({ post: await loadPost(params.slug) }),
}),
},
});
```
```svelte
{post.title}
```
The main component used with Mochi.page() renders on the server, so you can also call data helpers directly inside the component instead of threading every value through props. Client-side interactivity is opt-in per child component via `mochi:hydrate`, `mochi:hydrate:visible`, `mochi:defer`, or `mochi:defer:visible`.
You don't need to thread `params` through `serverProps`; instead, you can call `getRequestContext().params` directly anywhere on the server.
### Form actions
SvelteKit's `actions` export becomes the `actions` field on `Mochi.page`. The helpers are named the same: `fail`, `redirect`, `success`, imported from `mochi-framework`. POST submissions match an `?/` query (or `default` when absent), and the action's return value populates a `form` prop on re-render. The action callback receives `{ request, url, server, locals, kind, method, formData, actionName, cookies, params }`.
```ts
// file (SvelteKit): src/routes/login/+page.server.ts
import { fail, redirect } from '@sveltejs/kit';
export const actions = {
default: async ({ request }) => {
const formData = await request.formData();
const username = String(formData.get('username') ?? '');
if (!username) return fail(400, { error: 'Username required' });
return { username };
},
logout: () => redirect(303, '/'),
};
```
```ts
// file (Mochi): src/index.ts
import { Mochi, fail, success, redirect } from 'mochi-framework';
await Mochi.serve({
routes: {
'/login': Mochi.page('./src/Login.svelte', {
actions: {
default: ({ formData, cookies }) => {
const username = String(formData.get('username') ?? '');
if (!username) return fail(400, { error: 'Username required' });
cookies.set('user', username, { httpOnly: true, path: '/' });
return success({ username });
},
logout: () => redirect(303, '/'),
},
}),
},
});
```
```svelte
{#if form?.error}
{form.error}
{/if}
```
When `actions` is declared, leave `form` reserved for the action result — don't return it from `serverProps`. See [Defining routes](/docs/defining-routes/).
### `use:enhance`
SvelteKit's `use:enhance` action becomes Mochi's `enhance` attachment from `mochi-framework`. The wire format and `submit` callback semantics match closely (`MochiEnhanceResult` mirrors SvelteKit's `ActionResult`).
```svelte
```
```svelte
```
Mark the surrounding component with `mochi:hydrate*` so the attachment can attach in the browser — attachments can only run when Svelte hydrates a component. See [Progressively enhancing forms with enhance](/docs/progressively-enhancing-forms-with-enhance/).
### API routes (`+server.ts`)
SvelteKit's `+server.ts` with `GET` / `POST` exports becomes `Mochi.api(handler)` — one handler per route, branch on `method` inside. The handler receives `{ request, url, server, locals, kind, method, params, cookies }`.
```ts
// file (SvelteKit): src/routes/api/users/[id]/+server.ts
export async function GET({ params }) {
return Response.json(await loadUser(params.id));
}
export async function POST({ params, request }) {
return Response.json(await createUser(params.id, await request.json()));
}
```
```ts
// file (Mochi): src/index.ts
import { Mochi, error } from 'mochi-framework';
await Mochi.serve({
routes: {
'/api/users/:id': Mochi.api(async ({ method, request, params }) => {
if (method === 'GET') return Response.json(await loadUser(params.id));
if (method === 'POST') return Response.json(await createUser(params.id, await request.json()));
error(405, 'Method not allowed');
}),
},
});
```
Use `Mochi.page` for normal HTML routes — `Mochi.api` never goes through the error page or `handleError`. See [API routes](/docs/api-routes/).
### `error()`, `redirect()`, `fail()`
Same names, imported from `mochi-framework`. `error(status, message)` throws `MochiHttpError`; `redirect(status, location)` returns from an action; `fail(status, data)` and `success(data?)` round-trip via the `form` prop or the `enhance` envelope.
```ts
// SvelteKit
import { error, redirect, fail } from '@sveltejs/kit';
```
```ts
// Mochi
import { error, redirect, fail, success } from 'mochi-framework';
```
Call `error(status, message)` to signal an HTTP status — a bare `throw new Error()` becomes a 500.
### Error pages (`+error.svelte`)
SvelteKit's `+error.svelte` becomes the `errorPage` option on `Mochi.serve()` — a single component for the whole app (defaults to `DefaultError.svelte`). The component receives a single `error: MochiErrorProps` prop with `status`, `message`, and `stack` (dev only).
```svelte
```
See also [Error handling](/docs/error-handling/).
### Hooks (`handle`)
SvelteKit's `hooks.server.ts` `handle` export becomes the `handle` option on `Mochi.serve()`. The shape matches: `async ({ event, resolve }) => Response`, `event` carries `{ request, url, server, locals, kind }`, and `sequence(...handles)` composes them. Unlike SvelteKit, `event` itself does not carry `cookies`, `params`, or `getClientAddress` — read those from `getRequestContext()` inside the handler.
```ts
// file (SvelteKit): src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
export const handle: Handle = async ({ event, resolve }) => {
event.locals.user = await loadUser(event.request);
return resolve(event);
};
```
```ts
// file (Mochi): src/handle.ts
import type { Handle } from 'mochi-framework';
export const auth: Handle = async ({ event, resolve }) => {
if (event.kind === 'asset') return resolve(event);
event.locals.user = await loadUser(event.request);
return resolve(event);
};
```
```ts
// file (Mochi): src/index.ts
import { Mochi, sequence } from 'mochi-framework';
import { auth } from './handle';
await Mochi.serve({ handle: sequence(auth), routes });
```
Wrap multiple handles in `sequence()` — `handle` takes a single function. See [Middleware (hooks)](/docs/middleware/).
### `resolve` options
SvelteKit's `resolve(event, { transformPageChunk, filterSerializedResponseHeaders })` maps to Mochi's `resolve(event, { transformPage, filterResponseHeaders })`. `transformPage({ html, done })` rewrites the HTML body; `filterResponseHeaders(name, value)` keeps or drops a header.
```ts
// file (SvelteKit): src/hooks.server.ts
export const handle = ({ event, resolve }) =>
resolve(event, {
transformPageChunk: ({ html }) => html.replace('%THEME%', 'dark'),
filterSerializedResponseHeaders: (name) => name.toLowerCase() !== 'server',
});
```
```ts
// file (Mochi): src/handle.ts
import type { Handle } from 'mochi-framework';
export const stripServer: Handle = ({ event, resolve }) =>
resolve(event, {
transformPage: ({ html }) => html.replace('%THEME%', 'dark'),
filterResponseHeaders: (name) => name.toLowerCase() !== 'server',
});
```
### `handleError`
Same name. Configure as a `Mochi.serve()` option; the hook receives `{ error, event, status, message }` and may return `{ status, message }`, a `Response`, or `void`.
```ts
// file (SvelteKit): src/hooks.server.ts
import type { HandleServerError } from '@sveltejs/kit';
export const handleError: HandleServerError = ({ error, event }) => {
tracker.capture(error, { path: event.url.pathname });
return { message: 'Internal error' };
};
```
```ts
// file (Mochi): src/index.ts
import type { HandleError } from 'mochi-framework';
const handleError: HandleError = ({ error, event }) => {
if (error) tracker.capture(error, { path: event.url.pathname });
};
await Mochi.serve({ handleError, routes });
```
`handleError` is never called for `Mochi.api` failures — return an error envelope inside the handler instead.
### `event.locals`
Same surface. Set `event.locals.x` from middleware; read it from any server-side code via `getRequestContext().locals`.
```ts
// file (SvelteKit): src/routes/profile/+page.server.ts
export const load = ({ locals }) => ({ user: locals.user });
```
```ts
// file (Mochi): src/SomePage.svelte
import { getRequestContext } from 'mochi-framework';
const { locals } = getRequestContext();
```
### Observability
SvelteKit's experimental OpenTelemetry tracing has no direct equivalent. Instead, Mochi emits structured lifecycle events through `mochiEvents` — `request`, `ws:*`, `sse:*`, `cache:*`, `action:*`, `server:start`, `server:stop`, and the compile-time events. Subscribe to hook tracing, metrics, or a custom logger in front of them.
```js
// file (SvelteKit): svelte.config.js
export default {
kit: { experimental: { tracing: { server: true }, instrumentation: { server: true } } },
};
// then write OTel setup in src/instrumentation.server.ts
```
```ts
// file (Mochi): src/index.ts
import { mochiEvents } from 'mochi-framework';
mochiEvents.on('request', ({ method, path, status, duration }) => {
metrics.timing('http.request', duration, { method, path, status });
});
```
`consoleLogger()` is the default subscriber for human-readable output. See [Events](/docs/events/).
### Client IP (`getClientAddress`)
SvelteKit's `event.getClientAddress()` becomes a method on `getRequestContext()`. Configure `proxy.addressHeader` / `proxy.xffDepth` on `Mochi.serve()` for trusted reverse-proxy hops.
```ts
// file (SvelteKit): src/routes/api/whoami/+server.ts
export const GET = ({ getClientAddress }) => new Response(getClientAddress());
```
```ts
// file (Mochi): src/handle.ts
import { getRequestContext } from 'mochi-framework';
const ip = getRequestContext().getClientAddress();
```
Set `proxy.xffDepth` so `getClientAddress()` picks the correct hop, instead of parsing `X-Forwarded-For` yourself.
### CSRF protection
Like SvelteKit, Mochi rejects cross-origin form POSTs by default (Origin header check on `POST`/`PUT`/`PATCH`/`DELETE` when the body is a form content type). Configure via the `csrf` option on `Mochi.serve()` and the `csrf:trustedOrigins`, `csrf:protectedMethods`, `csrf:formContentTypes`, `csrf:check` filters.
```js
// file (SvelteKit): svelte.config.js
export default {
kit: { csrf: { checkOrigin: true } }, // default
};
```
```ts
// file (Mochi): src/index.ts
await Mochi.serve({
csrf: { trustedOrigins: ['https://embed.example'] },
routes,
});
```
Pin trusted origins via `csrf.trustedOrigins` or the `csrf:trustedOrigins` filter instead of disabling CSRF on a state-mutating endpoint.
### Cookies
`event.cookies` becomes `getRequestContext().cookies` (also surfaced on the form-action callback as `event.cookies`). Same `get` / `set` / `delete` API and `CookieSerializeOptions`. App-wide defaults live on the `cookie:defaults` filter rather than per-call.
```ts
// file (SvelteKit): src/routes/login/+page.server.ts
export const actions = {
default: ({ cookies }) => {
cookies.set('session', token, { httpOnly: true, sameSite: 'lax', path: '/' });
},
};
```
```ts
// file (Mochi): src/handle.ts
import { getRequestContext } from 'mochi-framework';
const { cookies } = getRequestContext();
cookies.set('session', token, { httpOnly: true, sameSite: 'Lax', path: '/' });
```
### `$app/state` and the `page` store
There is no reactive `page` store. Instead, import `url`, `params`, `cookies`, and `locals` directly from `mochi-framework`. `url` is isomorphic — it reads from the request context on the server and from `window.location` on the client. `params` and `locals` are server-only.
```svelte
{page.url.pathname} — {page.params.slug}
```
```svelte
{url.pathname} — {params.slug}
```
`url` works on both server and client — no need to branch on environment. `params` and `locals` are server-only; guard them with `isServer` if needed. See [Request context](/docs/request-context/).
### `$app/navigation` (`goto`, `invalidate`, `preloadData`)
No equivalent. Mochi has no client-side router, so there is nothing to `goto` into and nothing to `invalidate` — every navigation is a full HTML round-trip. For form submissions, use `enhance()`; for anything else, set `window.location.href` or call `history.pushState` from a hydrated island.
```svelte
```
```svelte
```
Listen for `beforeunload` or `popstate` directly — there is no `beforeNavigate` / `afterNavigate` / `onNavigate`.
### Link options (`data-sveltekit-preload-*`)
No equivalent. There is no client router to preload code or data into — the browser handles `` clicks natively. `data-sveltekit-reload`, `data-sveltekit-replacestate`, `data-sveltekit-keepfocus`, and `data-sveltekit-noscroll` likewise have no Mochi attribute.
```html
About
```
```html
About
```
### Snapshots
No equivalent. SvelteKit's `snapshot` exists because its client router reuses page components across navigation; Mochi does full-page reloads, so the browser's bfcache restores native `` / `