Skip to content
Ilha
Esc
↑↓navigate↵open⌘Jpreview
On this page

Server islands

Render selected components on the server with Oxide, frames, and oxidejs actions.

Use Oxide when a component must close over server data. You keep the SPA authoring model. Files named *.server.tsx never ship their render function to the browser.

import const pages: (options?: IlhaPagesOptions) => Plugin
Vite plugin — use via `@ilha/router/vite`.
pages
from "@ilha/router/vite";
import const oxide: (options?: OxidejsOptions | undefined) => import("vite").Plugin<any>[] | import("vite").Plugin<any>oxide from "oxidejs/vite"; import { function defineConfig(config: UserConfig): UserConfig (+5 overloads)
Type helper to make it easier to use vite.config.ts accepts a direct {@link UserConfig } object, or a function that returns it. The function receives a {@link ConfigEnv } object.
defineConfig
} from "vite";
export default function defineConfig(config: UserConfig): UserConfig (+5 overloads)
Type helper to make it easier to use vite.config.ts accepts a direct {@link UserConfig } object, or a function that returns it. The function receives a {@link ConfigEnv } object.
defineConfig
({
UserConfig.plugins?: PluginOption[] | undefined
Array of vite plugins to use.
plugins
: [function oxide(options?: OxidejsOptions | undefined): import("vite").Plugin<any>[] | import("vite").Plugin<any>oxide({
OxidejsOptions.middleware?: (string | {
    module: string;
    imports?: string[];
})[] | undefined
Module specifiers whose default export is `(request, ctx) => Response | undefined | Promise<Response | undefined>`. Tried in order at the top of the production fetch handler; a Response short-circuits. Dev servers use connect middleware instead.
middleware
: ["@ilha/router/ssr"] }), function pages(options?: IlhaPagesOptions): Plugin
Vite plugin — use via `@ilha/router/vite`.
pages
()],
});

The middleware owns:

Endpoint Purpose
POST /__ilha/frame Render a server island again

There is no page loader endpoint. Fetch inside an async component, or yield from a streaming action.

One island on the server

Put the component in *.server.ts(x):

import * as Stream from "effect/Stream";
import { action } from "oxidejs";

export const deleteTask = action(async (id: string) => {
  await db.tasks.delete(id);
});

export const getTasks = action(async function* () {
  yield await db.tasks.list();
});

export const TaskList = async function TaskList() {
  return Stream.map(
    Stream.fromAsyncIterable(getTasks(), (error: unknown) =>
      error instanceof Error ? error : new Error(String(error))
    ),
    (list: { id: string; text: string }[]) => (
      <ul>
        {list.map((task) => (
          <li key={task.id}>
            {task.text}
            <button type="button" onclick={deleteTask.with(task.id)}>
              Remove
            </button>
          </li>
        ))}
      </ul>
    )
  );
};

Import it from a regular page:

import { TaskList } from "$lib/tasks.server";

export default function Home() {
  return <TaskList />;
}

The plugin replaces the server module with a client proxy. The proxy posts /__ilha/frame, morphs the HTML, and reconnects events.

Mark RPC functions with action from oxidejs. Bind them with .with(...) on event props so SSR can emit frame sentinels without running your handler during render.

Subscribe with Stream.fromAsyncIterable(getTasks()) so the scanner wires a live stream. Each yield refetches the frame.

Whole route on the server

Name the page foo.server.tsx. Export a default component. The client still mounts a proxy and pulls the first frame.

Auth

Frames are deny-by-default in production. For a public demo:

import { const setFrameAuth: (policy: FrameAuthPolicy) => void
Install the frame-authorization policy consumed by the production `@ilha/router/ssr` handler. `trustedOrigins` and `csrf` are also applied by the dev middleware (via `IlhaPagesOptions`).
setFrameAuth
} from "@ilha/router/ssr";
function setFrameAuth(policy: FrameAuthPolicy): void
Install the frame-authorization policy consumed by the production `@ilha/router/ssr` handler. `trustedOrigins` and `csrf` are also applied by the dev middleware (via `IlhaPagesOptions`).
setFrameAuth
({ FrameAuthPolicy.defaultAction?: "open" | "deny" | undefined
Action taken when no frame guard is registered. `"deny"` (default in the production handler) rejects every `/__ilha/frame` request with 403; `"open"` preserves the legacy unauthenticated behavior. The dev middleware stays permissive unless a guard is registered.
defaultAction
: "open" });

Install setFrameGuard() when islands serve private data. See Middleware and security.

Was this page helpful?