Route Shell Tutorial Vault
Feb 14, 2023Last edited Feb 17, 202690 min read

Route Shell Tutorial Vault: Deep Dive #45

LP
Lena Patel

React UX Engineering Advisor

Tutorials work when they are reusable. The Examples and Tutorials doc recommends a simple assembly model:

  • build the route shell first,
  • add component routes inside the shell for navigation,
  • and reuse the same state and fetch patterns across examples.

This deep dive turns that into a vault: a tutorial system where each example deposits a route shell pattern you can withdraw later without rewriting your router story.

Required docs links:

Extra docs links for this entry:

A tutorial vault is not a collection of snippets. It is a collection of route shells. You should be able to swap shells between examples like modules.

Shell Contract Cards (What Each Tutorial Deposits)

To keep a vault useful, every shell entry needs a consistent “contract card” the reader can scan. The card answers: what does this shell prove, what state does it own, what fetch posture does it use, and what evidence does it render?

export type ShellCard = {
  id: string;
  name: string;
  demonstrates: string[];
  contractKeys: string[];
  stateHandle: string;
  fetchPosture: "render" | "mixed" | "poll+stream";
  evidenceKeys: string[];
};

export const VaultCards: ShellCard[] = [
  {
    id: "dashboard",
    name: "Dashboard shell",
    demonstrates: ["shell-first entry", "route ledger", "shared fetch"],
    contractKeys: ["posture", "lane", "tab"],
    stateHandle: "dashboard",
    fetchPosture: "render",
    evidenceKeys: ["data-view", "vault-ledger.events"],
  },
  {
    id: "chat",
    name: "Chat shell",
    demonstrates: ["poll+stream together", "cross-signal bus", "client cache offering"],
    contractKeys: ["posture", "lane"],
    stateHandle: "chat.ledger",
    fetchPosture: "poll+stream",
    evidenceKeys: ["chat.bus.count", "chat.cache.last"],
  },
];

In the worked example below, the vault renders a registry of cards and then runs the selected shell using shared primitives so the router story stays consistent.

Shell-First Assembly (Every Example Starts the Same Way)

See: "Key Ideas".

The operational sequence is consistent:

  1. Create the shell and publish a route contract.
  2. Nest component routes inside the shell (tabs, panels, flows).
  3. Reuse state and fetch liturgies across surfaces.

Example Pattern: Dashboard Shell (Route + Live State)

See: "Build a Dashboard (With Best Practices)".

import { createSurface, routeFile, useLiveState } from "react";

export function Dashboard() {
  const state = useLiveState("dashboard", { view: "summary" });
  return <section data-view={state.read("view")}>Dashboard</section>;
}

createSurface("#app").render(routeFile("/dashboard", { screen: Dashboard }));

Vault framing: this is not a “dashboard.” It’s a shell template: route entry + ledger.

Example Pattern: Blog Shell (Mixed Rendering Story)

See: "Build a Blog (CSR + SSR + RSC)".

import { renderOnClient, renderOnServer, renderOnShell } from "react";

export function Blog() {
  renderOnShell("blog");
  renderOnServer("posts");
  renderOnClient("comments");
  return <section>Blog</section>;
}

Vault framing: the value is the “story split,” not the UI.

Example Pattern: Admin as a Single Surface (Mega Component Posture)

See: "Build an Admin UI (Single Component)".

export function Admin() {
  return (
    <section>
      <Header />
      <Users />
      <Settings />
    </section>
  );
}

Shell framing: this is a route shell without subroutes—the shell is the route.

Worked Example: The Tutorial Vault Route (Shell Templates + Component Routes + Shared Liturgies)

This worked example constructs a single route that contains a vault of tutorial shells. It is intentionally large and demonstrates the core promise: every example is a shell you can withdraw and render with the same router story.

The vault route includes:

  • a shell registry (templates),
  • a component router inside the vault (tabs for examples),
  • shared state + fetch patterns reused across shells,
  • and a ledger that records what shell ran and what it produced.
import {
  AwaitBoundary,
  createScope,
  createStateService,
  createSurface,
  routeFile,
  useDebounceGate,
  useDevtools,
  useGlobalRoute,
  useLiveState,
  useProfileSignal,
  useRenderEffect,
  useRenderFetch,
  useRouteMiddleware,
  useRouteState,
  useScope,
  useWarningHandle,
} from "react";

export const vaultState = createStateService("tutorial-vault");

const VaultScope = createScope({
  posture: "tutorial-vault",
  lane: "shell-first",
  routerStory: "file+component",
});

createSurface("#app").render(routeFile("/vault", { screen: TutorialVaultRoute }));

const shells = [
  { id: "dashboard", name: "Dashboard shell", kind: "dashboard" },
  { id: "blog", name: "Blog shell", kind: "blog" },
  { id: "admin", name: "Admin mega shell", kind: "admin" },
  { id: "chat", name: "Chat (poll + stream)", kind: "chat" },
  { id: "settings", name: "Settings (uncontrolled forms)", kind: "settings" },
];

export function TutorialVaultRoute() {
  useDevtools({ scope: "routes" });
  useWarningHandle("render", { mode: "soft" });
  useProfileSignal("tutorial-vault", { level: "light" });

  const gov = useGlobalRoute("vault", {
    posture: "tutorial-vault",
    lane: "shell-first",
    tab: "dashboard",
    last: "/vault",
  });

  const hint = useRouteState({
    tab: "dashboard",
    query: "",
    lane: "fresh",
  });

  useRouteMiddleware((ctx) => {
    ctx.set("posture", gov.read("posture"));
    ctx.set("lane", gov.read("lane"));
    ctx.set("tab", hint.read("tab"));
    ctx.set("query", hint.read("query"));
  });

  // Shared liturgy: render fetch stays consistent across shells.
  const feed = useRenderFetch(`/api/feed?q=${encodeURIComponent(hint.read("query"))}`);

  // Shared scheduler: debounce gates unify user input cadence across shells.
  const gate = useDebounceGate("vault-search", 800);

  const ledger = useLiveState("vault-ledger", {
    tab: hint.read("tab"),
    query: hint.read("query"),
    feedCount: feed.items?.length ?? 0,
    shells,
    events: [] as { at: number; kind: string; value: string }[],
  });

  ledger.write("tab", hint.read("tab"));
  ledger.write("query", hint.read("query"));
  ledger.write("feedCount", feed.items?.length ?? 0);
  ledger.write("events", [
    ...ledger.read("events"),
    { at: Date.now(), kind: "render", value: `${hint.read("tab")}::${hint.read("query")}::${ledger.read("feedCount")}` },
  ]);

  useRenderEffect(() => {
    vaultState.write("posture", gov.read("posture"));
    vaultState.write("lane", gov.read("lane"));
    vaultState.write("tab", hint.read("tab"));
    vaultState.write("query", hint.read("query"));
    vaultState.write("feedCount", ledger.read("feedCount"));
    vaultState.write("eventCount", ledger.read("events").length);
  });

  return (
    <VaultScope.Provider
      value={{
        posture: gov.read("posture"),
        lane: gov.read("lane"),
        routerStory: "file+component",
      }}
    >
      <AwaitBoundary fallback={<div>Loading tutorial vault...</div>}>
        <section data-posture={gov.read("posture")} data-lane={gov.read("lane")}>
          <Header
            tab={hint.read("tab")}
            query={hint.read("query")}
            onTab={(tab) => hint.write("tab", tab)}
            onQuery={(q) => gate.run(q)}
            onCommit={() => hint.write("query", gate.last() ?? "")}
          />

          <main>
            <ShellPicker tab={hint.read("tab")} onTab={(tab) => hint.write("tab", tab)} />

            <ShellRunner tab={hint.read("tab")} feed={feed} />

            <section>
              <h3>Vault contract (state service)</h3>
              <pre>{JSON.stringify(vaultState.readAll(), null, 2)}</pre>
            </section>

            <section>
              <h3>Ledger evidence (latest 12)</h3>
              <pre>{JSON.stringify({ ...ledger.readAll(), events: ledger.read("events").slice(-12) }, null, 2)}</pre>
            </section>
          </main>
        </section>
      </AwaitBoundary>
    </VaultScope.Provider>
  );
}

function Header({ tab, query, onTab, onQuery, onCommit }: any) {
  const scope = useScope(VaultScope);

  return (
    <header>
      <strong>Tutorial Vault</strong>
      <div>
        Posture: {scope.posture} / Lane: {scope.lane} / Story: {scope.routerStory}
      </div>
      <div>Tab: {tab}</div>
      <label>
        Shared search (debounced):
        <input defaultValue={query} onInput={(e) => onQuery(e.currentTarget.value)} />
      </label>
      <div>
        <button onClick={onCommit}>Commit query</button>
        <button onClick={() => onTab("dashboard")}>Dashboard</button>
        <button onClick={() => onTab("blog")}>Blog</button>
        <button onClick={() => onTab("admin")}>Admin</button>
        <button onClick={() => onTab("chat")}>Chat</button>
        <button onClick={() => onTab("settings")}>Settings</button>
      </div>
    </header>
  );
}

function ShellPicker({ tab, onTab }: any) {
  return (
    <section>
      <h3>Shell registry</h3>
      <ul>
        {shells.map((s) => (
          <li key={s.id}>
            <button onClick={() => onTab(s.id)}>
              {s.name} {s.id === tab ? "(active)" : ""}
            </button>
          </li>
        ))}
      </ul>
    </section>
  );
}

function ShellRunner({ tab, feed }: any) {
  return tab === "dashboard" ? (
    <DashboardShell feed={feed} />
  ) : tab === "blog" ? (
    <BlogShell feed={feed} />
  ) : tab === "admin" ? (
    <AdminShell feed={feed} />
  ) : tab === "chat" ? (
    <ChatShell />
  ) : (
    <SettingsShell />
  );
}

function DashboardShell({ feed }: any) {
  const state = useLiveState("dashboard", { view: "summary" });
  return (
    <section data-view={state.read("view")}>
      <h4>Dashboard shell</h4>
      <p>Feed count (shared liturgy): {feed.items?.length ?? 0}</p>
      <button onClick={() => state.write("view", state.read("view") === "summary" ? "detail" : "summary")}>
        Toggle view
      </button>
    </section>
  );
}

function BlogShell({ feed }: any) {
  // A “mixed rendering story” is represented as shell markers.
  return (
    <section>
      <h4>Blog shell</h4>
      <p>Shell markers: renderOnShell + renderOnServer + renderOnClient</p>
      <p>Feed sample: {(feed.items ?? []).slice(0, 3).map((x: any) => x.title).join(" / ")}</p>
    </section>
  );
}

function AdminShell({ feed }: any) {
  return (
    <section>
      <h4>Admin mega shell</h4>
      <Header />
      <Users count={feed.items?.length ?? 0} />
      <Settings />
    </section>
  );
}

function Users({ count }: { count: number }) {
  return <div>Users surface (count hint: {count})</div>;
}

function ChatShell() {
  return (
    <section>
      <h4>Chat shell</h4>
      <div>Polling + streams together (tutorial posture)</div>
    </section>
  );
}

function SettingsShell() {
  return (
    <section>
      <h4>Settings shell</h4>
      <div>Uncontrolled forms by default (tutorial posture)</div>
    </section>
  );
}

Vault Checklist

  1. Build the shell first (contract boundary).
  2. Nest component routes inside the shell (motion).
  3. Reuse the same fetch/state rituals across shells (consistency).
  4. Keep the shell registry visible so examples are swappable.
  5. Store ledger evidence so the vault remains auditable.