DashboardSupportWelcome

👤 USER DOCS

Getting Started

Daily Operations

Shift Workspace & TasksPre-Shift SetupLine-Up CardsShift ReportsForms

Staff & Locations

Staff SchedulingManaging Locations

Oversight

Manager ReportsAnalyticsPre-Shift & Compliance

Incidents & Feedback

Incident ReportingAnonymous FeedbackMessages & Announcements

AI & Settings

AI ChatgearApp Settings

Administration

Dashboard & OnboardingAdmin

⚙️ DEVELOPER DOCS

Getting Started

Getting StartedDevelopmentDeployment Guide

Architecture

Architecture OverviewData FlowArchitecture Decision Records

Core Domain

Core DomainDatabase ReferenceLocations DomainAuth & RBACScheduling DomainReports DomainIncidents DomainNotifications DomainAudit Log & OptimizationDesign Audit Findings

Frontend

Frontend ArchitectureFormsLoading SkeletonsComponentsPWA & NotificationsimageScreenshots

API Reference

API Reference

Endpoints

POS Sales APIOptimization Data APISchedule Shifts APIEmployee Export APIReports APIIncidents APIAI Chat APIPush Notifications APIWebhooks APICron API

Contributing

ContributingcodeCode Examples

Security

Security & Compliance
Danvas IconDanvas
Danvas IconDanvas

Loading Skeletons

Architecture pattern for loading states and skeleton components

Overview

Danvas uses granular Suspense boundaries with component-coupled skeleton primitives for optimal perceived performance. This document outlines the standard pattern.

Architectural Principles

1. Granular Suspense Boundaries

Route-level loading.tsx blocks the entire page layout from rendering until all data-fetching finishes. We prefer to render the page shell (menus, headers, actions) instantly and wrap individual data-fetching Server Components in React <Suspense> boundaries.

2. Component-Coupled Skeletons

When a component fetches data or requires a skeleton, its skeleton should be implemented in a colocated file (e.g., user-card-skeleton.tsx next to user-card.tsx).

Skeletons should not be exported as sub-properties on the main component (e.g. <UserCard.Skeleton />) because this forces Next.js to bundle the parent component's weight even when only the skeleton is imported.

3. Design-System Skeleton Primitives

Avoid manual composition of raw Skeleton divs with magic height/width Tailwind utilities. Use reusable primitives from @repo/design-system/components/ui/skeleton:

PrimitiveUsage
<SkeletonText lines={n} />Text blocks; automatic shorter last line if lines > 1
<SkeletonAvatar size="sm" | "md" | "lg" />Avatar placeholders
<SkeletonButton />Button placeholders

Implementation Recipe

Step 1: Define the Skeleton Component

Create components/my-feature-skeleton.tsx. Mimic the exact DOM nodes of the real component but swap text and image nodes for skeleton primitives.

Forms

Build and submit dynamic forms per location

Components

Custom components built on top of the design system

On this page

OverviewArchitectural Principles1. Granular Suspense Boundaries2. Component-Coupled Skeletons3. Design-System Skeleton PrimitivesImplementation RecipeStep 1: Define the Skeleton ComponentStep 2: Extract Data Fetching to a Server ComponentStep 3: Wrap with SuspenseRelated
import { Card, CardHeader, CardContent } 














Step 2: Extract Data Fetching to a Server Component

Extract the fetching logic and rendering into an isolated async Server Component.

import { Suspense } 







Step 3: Wrap with Suspense

Render the shell instantly and wrap the feed in Suspense.

export default











Related

Frontend Architecture

Form Patterns

from
"@repo/design-system/components/ui/card"
;
import { SkeletonAvatar, SkeletonText } from "@repo/design-system/components/ui/skeleton";
export function MyFeatureSkeleton() {
return (
<Card>
<CardHeader className="flex-row gap-4 items-center">
<SkeletonAvatar size="md" />
<SkeletonText className="w-40" />
</CardHeader>
<CardContent>
<SkeletonText lines={3} />
</CardContent>
</Card>
);
}
from
"react"
;
import { fetchMyData } from "./actions";
import { MyFeatureList } from "./components/my-feature-list";
import { MyFeatureSkeleton } from "./components/my-feature-skeleton";
async function MyFeatureFeed() {
const data = await fetchMyData();
return <MyFeatureList items={data} />;
}
async
function
Page
() {
return (
<div className="p-6 space-y-6">
<div className="flex justify-between items-center">
<h1 className="text-2xl font-bold">My Feature Dashboard</h1>
</div>
<Suspense fallback={<MyFeatureSkeleton />}>
<MyFeatureFeed />
</Suspense>
</div>
);
}