🍡 mochi

SSR framework for Svelte 5 + Bun with islands-based selective hydration

On this page

Request context

Inside components and server-side helpers, import context values directly from mochi-framework:

import { url, params, cookies, locals } from 'mochi-framework';

Each export is a proxy that reads from the current request’s AsyncLocalStorage context on every property access — no need to thread values through props.

url

The current page URL as a standard URL object.

<!-- file: src/Post.svelte -->
<script>
  import { url } from 'mochi-framework';

  const page = Number(url.searchParams.get('page') ?? '1');
</script>

<p>Current path: {url.pathname}</p>

url is isomorphic — it works on both server and client:

  • Server: proxies getRequestContext().url, the parsed request URL.
  • Client: proxies new URL(window.location.href), constructed fresh on each access so it always reflects the current browser URL (including after pushState / replaceState).

params

Route parameters matched by the Bun router. Server-only.

<!-- file: src/Post.svelte -->
<script>
  import { params } from 'mochi-framework';
</script>

<h1>{params.slug}</h1>

cookies

Read and write cookies on both server and client through one API. See the Cookies demo for a full example.

<script>
  import { cookies, isBrowser } from 'mochi-framework';

  const theme = cookies.get('theme') ?? 'light';
</script>

locals

Per-request data set by middleware. Server-only.

// file: src/middleware.ts
import type { Handle } from 'mochi-framework';

export const auth: Handle = async ({ event, resolve }) => {
  event.locals.user = await getUser(event.cookies);
  return resolve(event);
};
<!-- file: src/Dashboard.svelte -->
<script>
  import { locals } from 'mochi-framework';

  const user = locals.user;
</script>

getRequestContext()

Returns the full context object with all fields. Server-only. Prefer the individual exports above unless you need multiple fields at once.

import { getRequestContext } from 'mochi-framework';

const { url, params, cookies, locals, request, requestId } = getRequestContext();

Do NOT call getRequestContext() or access params / locals on the client; they throw. Use isServer to guard server-only branches. url and cookies work on both sides.

The context also carries isWarmuptrue when the request was issued by route warmup at startup, not a real client. Guard side effects in serverProps that shouldn’t fire for synthetic warmup hits:

serverProps: async () => {
  const ctx = getRequestContext();
  if (!ctx.isWarmup) await recordVisit(ctx.url.pathname); // skip warmup
  return { posts: await loadPosts() };
};