Route-matched boot skeletons
What this solves
Visual and information. A generic spinner tells the reader nothing about where they landed. Dispatching the boot frame on the URL lets the destination's own icon, title, and static copy paint on the very first frame, so the app already looks like the page asked for before a single byte of real data has come back.
Use it when
- Every top-level route during the auth-boot windowwalmart-mvp's routeBootSkeleton(pathname), the dispatcher every route resolves through before its data loads
- A record route whose header facts are already known from the URLspeedway's job detail route, where the back link and the file-count subtitle render before the record itself arrives
Rendered
Fixture data; check both themes.Boot frame for /jobs
Jobs
| Job | Module | Owner | Status | Started |
|---|---|---|---|---|
patterns/boot-skeletons/example.tsxCopy this into a fresh route and it renders as above.
"use client";import { Button, Card, PageTitle, TableSkeleton } from "@versable-git/ui";import type { TableColumn } from "@versable-git/ui";import { useState } from "react";import { RecordSkeleton } from "./example-record";import { SettingsSkeleton } from "./example-settings";// Route-matched boot skeletons: the boot frame dispatches on pathname so the// first paint mirrors the destination's real layout, never a generic box.// The list route is the primary variant, kept inline; the record and// settings routes live beside this file since their layouts differ in code,// not just in props.const jobsColumns: TableColumn<Record<string, never>>[] = [ { key: "id", header: "Job" }, { key: "module", header: "Module" }, { key: "owner", header: "Owner" }, { key: "status", header: "Status" }, { key: "started", header: "Started" },];function ListSkeleton() { return ( <div className="flex flex-col gap-4"> <PageTitle Icon="Table" title="Jobs" subtitle="Every batch you've imported and exactly where it is in the pipeline. Click a row for the stage-by-stage view." /> <TableSkeleton columns={jobsColumns} rows={4} /> </div> );}const ROUTES = [ { path: "/jobs", label: "/jobs (list)", Frame: ListSkeleton }, { path: "/jobs/job_9421", label: "/jobs/job_9421 (record)", Frame: RecordSkeleton }, { path: "/settings", label: "/settings (settings)", Frame: SettingsSkeleton },];export function BootSkeletons() { const [path, setPath] = useState(ROUTES[0]!.path); const route = ROUTES.find((r) => r.path === path) ?? ROUTES[0]!; return ( <div className="flex flex-col gap-4"> <div className="flex flex-wrap gap-2"> {ROUTES.map((r) => ( <Button key={r.path} size="sm" variant={r.path === path ? "solid" : "outline"} content={`Boot on ${r.label}`} onClick={() => setPath(r.path)} /> ))} </div> <Card title={`Boot frame for ${route.path}`} noAnimate> <route.Frame /> </Card> </div> );}Where it ships
walmart-mvp/frontend/src/lib/routeSkeletons.tsx:24-35routeBootSkeleton(pathname): every top-level route maps to a skeleton mirroring that page's real static layout
App-specific: The real map covers every top-level route with a hand-built skeleton per page, and the dispatcher runs during the auth boot only; once data loads, the page's own Suspense fallbacks take over.