Skip to main content
All Docs
FeaturesBlockManOSUpdated March 26, 2026

Faster Maintenance Dashboard: Server-Side Data Prefetch

Faster Maintenance Dashboard: Server-Side Data Prefetch

Release: v1.0.77 · PERF-14

The /dashboard/maintenance page now loads its initial data on the server, eliminating a client-side round-trip that previously added ~200–400 ms to every first page load.

What Changed

Before (v1.0.76 and earlier)

The maintenance page was a thin React Server Component (RSC) that authenticated the session and then rendered a dynamically imported client component. The sequence on first load was:

  1. Server renders an empty shell and sends it to the browser.
  2. Browser hydrates the client component.
  3. Client fires two tRPC calls — maintenance.summary and maintenance.listRequests.
  4. Data arrives and the loading spinners disappear.

Step 3 is a full extra network round-trip that the user had to wait through before seeing any content.

After (v1.0.77)

The RSC page now resolves data before sending the HTML response:

  1. Server authenticates the session and resolves the user's orgId from orgMembers.
  2. Three database queries run in parallel with Promise.all:
    • Status counts for all maintenance requests (feeds the summary cards).
    • Count of urgent open requests.
    • First 50 requests with left joins to developments, units, contractors, and owners.
  3. The populated MaintenanceInitialData object is passed as a prop to MaintenanceDashboardClient.
  4. The client component seeds both maintenance.summary.useQuery and maintenance.listRequests.useInfiniteQuery with the server data so content is visible on first paint — no spinners, no skeleton screens.

Behaviour Details

ScenarioBehaviour
Initial page load (no filters)Summary cards and request list render immediately from server-prefetched data
Filter applied (status / priority / category)tRPC query fires normally and fetches filtered results
Mutation (create / update / delete)Invalidation and refetch work as before
User has no org membershipinitialData is null; component falls back gracefully
Pagination (load more)nextCursor from the server page is used as normal; subsequent pages are fetched client-side

MaintenanceInitialData Type

The shape of the server-prefetched data passed from page.tsx to client.tsx:

export interface MaintenanceInitialData {
  summary: {
    openCount: number;
    assignedCount: number;
    inProgressCount: number;
    pendingPartsCount: number;
    completedCount: number;
    urgentOpenCount: number;
    totalActive: number;
  };
  requests: {
    items: {
      id: string;
      title: string;
      category: string;
      priority: string;
      status: string;
      source: string;
      worksOrderRef: string | null;
      scheduledDate: string | null;
      completedDate: string | null;
      estimatedCost: string | null;
      actualCost: string | null;
      isUrgent: boolean;
      locationDescription: string | null;
      createdAt: Date;
      updatedAt: Date;
      developmentId: string;
      developmentName: string | null;
      unitId: string | null;
      unitNumber: string | null;
      contractorId: string | null;
      contractorName: string | null;
      reportedByOwnerName: string | null;
    }[];
    nextCursor: string | undefined;
  };
}

Files Changed

FileChange
src/app/dashboard/maintenance/page.tsxRSC now prefetches summary and first 50 requests server-side
src/app/dashboard/maintenance/client.tsxAccepts initialData prop; seeds tRPC queries; removes nextDynamic skeleton
src/app/api/health/route.tsResponse now includes timestamp field
src/middleware.tsMatcher regex simplified; health bypass handled in middleware logic

Health Endpoint

GET /api/health is a public endpoint (no authentication required). It now returns:

{
  "status": "ok",
  "timestamp": "2025-01-15T12:34:56.789Z"
}

The timestamp field is an ISO 8601 string set at request time, useful for confirming the endpoint is live and returning fresh responses in uptime monitors.