Skip to main content
All Docs
FeaturesBlockManOSUpdated March 26, 2026

Dashboard Skeleton Loading States

Dashboard Skeleton Loading States

All major dashboard routes in BlockManOS display animated skeleton placeholders during page navigation. This is implemented using Next.js App Router's built-in streaming SSR support via loading.tsx files and a shared skeleton component library.

How It Works

Next.js App Router treats any loading.tsx file co-located with a page.tsx as a React Suspense boundary. When a user navigates to a route, Next.js immediately renders the loading.tsx content while the page's server component fetches its data. Once the data is ready, the skeleton is swapped out for the real page — no additional code or configuration is needed in the page components themselves.

/dashboard/owners/
  page.tsx        ← real page (data-fetching server component)
  loading.tsx     ← rendered instantly as Suspense fallback

Skeleton Component Library

All skeleton layouts live in src/components/skeleton.tsx. Import them directly into any loading.tsx or custom Suspense fallback.

Skeleton

The base animated pulse block. Use it directly with a className to create any arbitrary skeleton shape.

import { Skeleton } from "@/components/skeleton";

<Skeleton className="h-8 w-64" />

Renders a rounded rectangle with a bg-muted animate-pulse style.


PageSkeleton

For generic content pages: renders a page heading area followed by a single large bordered content card.

import { PageSkeleton } from "@/components/skeleton";

export default function Loading() {
  return <PageSkeleton />;
}

Used by: /dashboard/admin, /dashboard/annual-plan, /dashboard/communications, /dashboard/irish-legislation, /dashboard/owners/import, /dashboard/settings


TablePageSkeleton

For list/table pages: renders a toolbar row, a column-header row, and a configurable number of data rows.

import { TablePageSkeleton } from "@/components/skeleton";

export default function Loading() {
  return <TablePageSkeleton rows={8} />;
}
PropTypeDefaultDescription
rowsnumber6Number of placeholder data rows to render

Used by: /dashboard/activity (8), /dashboard/compliance (6), /dashboard/developments (8), /dashboard/gdpr (5), /dashboard/jobs (6), /dashboard/maintenance (8), /dashboard/omc (6), /dashboard/omc/cro-compliance (6), /dashboard/owners (7), /dashboard/psra (6), /dashboard/team (5)


FinancePageSkeleton

For finance and reporting pages: renders a row of 4 stat-cards followed by a main content area.

import { FinancePageSkeleton } from "@/components/skeleton";

export default function Loading() {
  return <FinancePageSkeleton />;
}

Used by: /dashboard (root), /dashboard/bank-reconciliation, /dashboard/budget, /dashboard/reports, /dashboard/service-charges, /dashboard/sinking-fund


SchedulePageSkeleton

For schedule and calendar pages: renders a filter/control row followed by a card grid.

import { SchedulePageSkeleton } from "@/components/skeleton";

export default function Loading() {
  return <SchedulePageSkeleton />;
}

Used by: /dashboard/agm, /dashboard/ppm


DocumentPageSkeleton

For document management pages: renders a search bar followed by a file grid.

import { DocumentPageSkeleton } from "@/components/skeleton";

export default function Loading() {
  return <DocumentPageSkeleton />;
}

Used by: /dashboard/documents


Adding a Skeleton to a New Route

  1. Choose the skeleton component that best matches the new page's layout (or use PageSkeleton as a general fallback).
  2. Create a loading.tsx file in the same directory as the new route's page.tsx.
  3. Export a default Loading function that returns the chosen skeleton.
// src/app/dashboard/my-new-route/loading.tsx
import { TablePageSkeleton } from "@/components/skeleton";

export default function Loading() {
  return <TablePageSkeleton rows={6} />;
}

No changes to page.tsx or any layout files are required.