Fixing Dashboard CLS with Skeleton Loading States
Fixing Dashboard CLS with Skeleton Loading States
Released in v1.0.54
Background
The SaaS Factory main dashboard is powered by DashboardContent, a Next.js server component. On every page load it fires five parallel data fetches:
- Projects — active and archived product listings
- Pipelines — current CI/CD run statuses
- Releases — recent version history
- Agent jobs — in-flight autonomous agent activity
- Pending approvals — items awaiting human sign-off
Because all five fetches happen server-side before any content is streamed to the client, the layout shell (sidebar, top nav) appeared instantly while the entire main content area stayed blank. The moment all fetches resolved, four stat cards, an attention banner, and two activity cards all appeared simultaneously — a classic cumulative layout shift (CLS) event that degrades both perceived performance and Core Web Vitals scores.
What Was Built
Suspense boundary with a skeleton fallback
DashboardContent is now wrapped in a React Suspense boundary. While the server fetches are in flight, the browser renders a skeleton fallback that mirrors the real dashboard grid:
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Stat │ │ Stat │ │ Stat │ │ Stat │
│ Card ░░░ │ │ Card ░░░ │ │ Card ░░░ │ │ Card ░░░ │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
┌───────────────────────┐ ┌───────────────────────┐
│ Activity Card ░░░░░░░ │ │ Activity Card ░░░░░░░ │
│ ░░░░░░░░░░░░░░░░░░░░░ │ │ ░░░░░░░░░░░░░░░░░░░░░ │
└───────────────────────┘ └───────────────────────┘
The skeleton uses the existing ui/skeleton component — no new dependencies were introduced.
Why this matters
- Immediate visual feedback. Users see the dashboard structure the moment the page loads rather than a blank white area.
- No layout shift. Because the skeleton occupies the same grid cells as the real content, nothing jumps or reflows when data arrives.
- Better Core Web Vitals. Reducing CLS improves Lighthouse scores and, by extension, SEO rankings for any product built on the factory.
- Consistency. The pattern is now established for all future server components that perform multi-source data fetching.
How It Works
Next.js App Router supports streaming through Suspense. When the browser requests the dashboard:
- The layout shell (HTML, sidebar, nav) is flushed to the client immediately.
- The
Suspensefallback (skeleton grid) is rendered and streamed in its place. - The server continues resolving all five data fetches in parallel.
- Once all fetches complete, React streams the real
DashboardContentHTML and seamlessly swaps it in — no client-side JavaScript fetch required.
No Breaking Changes
- Dashboard data sources and their fetch logic are unchanged.
- The skeleton fallback is additive — it only appears while data is loading.
- No new environment variables or configuration is required.