perf(web): add route-level loading skeletons for instant tab switching
The 'switching is laggy with many accounts' report — root cause is that both /page.tsx and /users/page.tsx are Server Components that block on the API fetch before sending any HTML. During the wait, the previous route stays frozen (no spinner, no feedback) — the user perceives a 'lag' that grows with row count. App Router's loading.tsx convention solves this: Next.js renders it INSTANTLY on navigation, then streams in the real RSC tree once the data fetch resolves. The skeleton matches the shape of the real shell + a few placeholder rows so the swap is layout-stable. Files: - web/components/table-skeleton.tsx — shared skeleton (PageHead + N rows) - web/app/loading.tsx — used for / - web/app/users/loading.tsx — used for /users If row counts keep growing past a few hundred and the table itself becomes the bottleneck (vs the network fetch this addresses), the next step is pagination: accept ?limit=&offset= on /acc/ and /user/ in cm_api.py and add a 'Load more' button (or a virtual list) at the table-component layer.
This commit is contained in:
parent
324c88e652
commit
9a4072129a
5
web/app/loading.tsx
Normal file
5
web/app/loading.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import TableSkeleton from "@/components/table-skeleton";
|
||||||
|
|
||||||
|
export default function Loading() {
|
||||||
|
return <TableSkeleton eyebrow="Table" title="Accounts" />;
|
||||||
|
}
|
||||||
5
web/app/users/loading.tsx
Normal file
5
web/app/users/loading.tsx
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import TableSkeleton from "@/components/table-skeleton";
|
||||||
|
|
||||||
|
export default function Loading() {
|
||||||
|
return <TableSkeleton eyebrow="Table" title="Users" />;
|
||||||
|
}
|
||||||
50
web/components/table-skeleton.tsx
Normal file
50
web/components/table-skeleton.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
type Props = { eyebrow: string; title: string; rows?: number };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lightweight skeleton that mimics the AccountsTable / UsersTable shell.
|
||||||
|
* Rendered by app/loading.tsx and app/users/loading.tsx — Next.js shows
|
||||||
|
* this immediately on tab navigation, then streams in the real Server
|
||||||
|
* Component once its data fetch resolves. Without it, the previous
|
||||||
|
* route's UI freezes until the fetch finishes (the "tab switch is
|
||||||
|
* laggy" symptom).
|
||||||
|
*
|
||||||
|
* The pulse animation comes from Tailwind's animate-pulse on each
|
||||||
|
* placeholder bar; no JS, no layout shift when the real content swaps in.
|
||||||
|
*/
|
||||||
|
export default function TableSkeleton({ eyebrow, title, rows = 8 }: Props) {
|
||||||
|
return (
|
||||||
|
<div className="space-y-8">
|
||||||
|
<div className="flex flex-wrap items-end justify-between gap-4">
|
||||||
|
<div>
|
||||||
|
<p className="text-[11px] font-medium uppercase tracking-wider text-zinc-500">
|
||||||
|
{eyebrow}
|
||||||
|
</p>
|
||||||
|
<h1 className="mt-1 text-2xl font-semibold tracking-tight text-zinc-900 sm:text-3xl">
|
||||||
|
{title}
|
||||||
|
<span className="ml-2 inline-block h-4 w-8 animate-pulse rounded bg-zinc-200 align-middle" />
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="h-8 w-20 animate-pulse rounded-full bg-zinc-200" />
|
||||||
|
<span className="h-8 w-16 animate-pulse rounded-full bg-zinc-200" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{Array.from({ length: rows }).map((_, i) => (
|
||||||
|
<li
|
||||||
|
key={i}
|
||||||
|
className="rounded-xl bg-white px-4 py-3 ring-1 ring-zinc-200/60"
|
||||||
|
>
|
||||||
|
<div className="flex items-center justify-between gap-3">
|
||||||
|
<div className="flex min-w-0 flex-1 items-center gap-3">
|
||||||
|
<span className="h-4 w-24 animate-pulse rounded bg-zinc-200" />
|
||||||
|
<span className="h-4 w-16 animate-pulse rounded-full bg-zinc-100" />
|
||||||
|
</div>
|
||||||
|
<span className="h-6 w-6 animate-pulse rounded-md bg-zinc-100" />
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user