Slots

A <RouteSlot> GETs another route after the page lands, on connect or when it becomes visible. Use that to keep dynamic data off a cached shell, or to defer heavy or below-the-fold work.

RouteSlot

<RouteSlot src="/cart" /> fetches after the host connects. fallback is optional then. The GET fills the host (innerHTML). The host stays, so patch.refresh can re-fetch that src. See Write handler.

html() on slot requests doesn't render any layouts. See Layouts.

home.tsx

import { CacheStrategy, type ReadArgs, RouteSlot } from "dashi";

export function Home({ html }: ReadArgs) {
  return html(
    <main>
      <RouteSlot src="/cart" />
    </main>,
    { cache: { strategy: CacheStrategy.Immutable } },
  );
}

visible

fetchWhen="visible" waits for the first intersection. fallback is required. patch.refresh on the same src may fetch before the slot intersects.

components/panel.tsx

import { RouteSlot } from "dashi";

export function Panel() {
  return (
    <RouteSlot
      src="/chart"
      fetchWhen="visible"
      fallback={<p>Loading</p>}
    />
  );
}

fallback

Shown until a successful body, or a nonempty error body, replaces it. On connect, omit it and the host is empty until the GET lands.

As a page

The same src is a normal document when you visit it. Layouts run on that request.

The client sends X-Slot on the slot GET. You can use that header when the slot markup should differ from the page.

profile.tsx

import type { ReadArgs } from "dashi";

export function Profile({ ctx, html }: ReadArgs) {
  const isSlot = ctx.req.headers.get("x-slot") !== null;
  if (isSlot) {
    return html(<Card />);
  }
  return html(<Page />);
}

Every sealed html() / patches() response sets Vary: x-slot. A custom CDN cache key must include that header, or a slot body and a document for the same URL can share one entry. See Read handler.

Nested slots

A slot body can render another <RouteSlot>. Each waits for its parent, so a deep nest waterfalls. A slot that includes itself, or an ancestor, loops.