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:
- Server renders an empty shell and sends it to the browser.
- Browser hydrates the client component.
- Client fires two tRPC calls —
maintenance.summaryandmaintenance.listRequests. - 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:
- Server authenticates the session and resolves the user's
orgIdfromorgMembers. - 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.
- The populated
MaintenanceInitialDataobject is passed as a prop toMaintenanceDashboardClient. - The client component seeds both
maintenance.summary.useQueryandmaintenance.listRequests.useInfiniteQuerywith the server data so content is visible on first paint — no spinners, no skeleton screens.
Behaviour Details
| Scenario | Behaviour |
|---|---|
| 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 membership | initialData 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
| File | Change |
|---|---|
src/app/dashboard/maintenance/page.tsx | RSC now prefetches summary and first 50 requests server-side |
src/app/dashboard/maintenance/client.tsx | Accepts initialData prop; seeds tRPC queries; removes nextDynamic skeleton |
src/app/api/health/route.ts | Response now includes timestamp field |
src/middleware.ts | Matcher 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.