Performance & Caching
CosmosCost layers render-time memoization, API caches, and lightweight client state updates to keep the dashboard responsive while protecting Supabase from expensive fan-out queries.
Server-Side Caching Layers
The dashboard page combines three caching strategies before any client code runs:
- •Render-time deduplication –
getDashboardOverviewSnapshotinsrc/lib/dashboard/get-dashboard-overview.tsis wrapped incache(), so identical argument sets only execute once per request even when multiple server components demand the same data. - •Incremental revalidation –
src/app/dashboard/[slug]/page.tsxexportsrevalidate = 300, caching the rendered HTML for 5 minutes before Next.js schedules a background refresh. - •API caches for follow-up fetches – expensive JSON payloads are guarded with
unstable_cacheso client re-fetches hit warm data instead of re-running Supabase RPC chains.
| Scope | Location | TTL | Cache Controls |
|---|---|---|---|
| Dashboard SSR render | src/app/dashboard/[slug]/page.tsx | 5 minutes | revalidate = 300 |
| Dashboard snapshot API | src/app/api/customers/[slug]/dashboard/snapshot/route.ts | 5 minutes | unstable_cache(..., { revalidate: 300, tags: ['dashboard'] }), s-maxage=300 |
| Daily cost trends API | src/app/api/customers/[slug]/costs/daily/route.ts | 1 hour | unstable_cache(..., { revalidate: 3600, tags: ['costs'] }), s-maxage=3600 |
| Admin analytics helpers | src/lib/api/admin-cache.ts | 5–60 minutes | unstable_cache with tag families such as 'admin:users' |
The snapshot endpoint takes a Supabase client from the request context before entering the cached callback to avoid cookie lookups inside the cache scope, then returns both the overview data and subscription snapshot using Promise.all. The cost trends API streams large result sets through processCostDataStream to prevent long event loops and attaches stale-while-revalidate headers so Vercel Edge can serve cached payloads while regeneration runs.
Client State & Preference Persistence
Optimistic UI keeps interactions instant while Supabase writes occur in the background:
- •
src/components/dashboard/DashboardClient.tsxmaintains the selected quick metrics and cost trend visualizations.persistQuickMetricSelectionandpersistTrendSelectionupdate React state immediately, triggerPATCHrequests, and roll back if the network responds with an error. - •Time period buttons call
router.pushwith updated?days=values so the server component re-renders with fresh data while preserving layout state. - •
DashboardLayouttoggles sections by updatingactiveSectionand usingwindow.history.pushState, avoiding a full navigation. When the user returns to Overview, the originalDashboardClientinstance renders, so state survives round-trips through Analytics or Reports. - •
SubscriptionProviderwraps the overview, analytics, reports, and billing sections. It receivesinitialSubscriptionfrom the server render, exposes the normalized plan snapshot to child components, and refreshes in the background only when a customer ID is present. - •The legacy
DashboardOverviewcomponent still hydrates historical props withuseRefguards (for examplequickMetricsHydratedRef) to suppress redundant API calls during the migration period.
User Preference Loading
User selections are stored per customer in Supabase:
- •
ensureQuickMetricPreferencesandensureCostTrendPreferencesinsrc/lib/dashboard/preferences.tsquery theuser_preferencestable, seed defaults when no row exists, and sanitize IDs before returning. - •
getDashboardOverviewSnapshotcalls those helpers inside the mainPromise.allalongside the cost aggregates, meaning quick metrics, cost trend IDs, activity history, and totals arrive as a single object that seeds the client. - •A cached batch helper (
ensureBatchDashboardPreferences) exists for RPC-based aggregation, but the current implementation relies on the explicit helpers above for clarity and easier error handling.
Cache Invalidation & Tagging
Supabase functions power cache-aware workflows so long-lived caches never drift:
- •
src/lib/dashboard/cache-revalidation.tsdefines reusable tag generators such asdashboard(customerId),providers(customerId), andproviderCosts(customerId, provider). - •Helper utilities (
invalidateDashboardMetrics,invalidateProviderCaches,invalidateServiceCaches, andinvalidateCustomerCache) call Supabase RPCs to coordinate cache clears after imports or account changes. - •Admin dashboards can refresh materialized views through
refreshCostAggregationViews, which wraps therefresh_cost_aggregation_viewsRPC and is safe to run inside background jobs.
Database Optimizations
Aggregations are handled directly in SQL to minimize application CPU time:
- •
fetchCostsByDate,fetchCostsByProvider, andfetchCostsByServicefirst attempt the_fastmaterialized view RPCs and fall back to the baseline functions if the optimized views have not been refreshed yet. - •
fetchCostSummaryaggregates total spend, providers, services, and record counts in a single RPC call so the application only performs lightweight number parsing. - •The daily costs API chunks large result sets through
processCostDataStream, yielding the event loop every 500 records to avoid blocking concurrent requests.
Key Performance Improvements
- •
src/app/dashboard/[slug]/loading.tsxrendersDashboardPageSkeleton, providing instant visual feedback during the initial server render and keeping perceived load time low. - •
DashboardLayoutlazily loads heavy client bundles such as analytics, billing, and cloud accounts pages withnext/dynamic, whileCostTrendSectiondefers chart rendering until Recharts is loaded. - •Chart calculations and headline metrics rely on memoized selectors inside
DashboardClient, ensuring rerenders are cheap even when new data arrives.
End-to-End Request Flow
- User visits
/dashboard/[slug]?days=30. Next.js reuses the cached HTML if the 5 minute window is still valid; otherwise the page is regenerated. getDashboardOverviewSnapshotruns (once per argument set), executing the cost RPCs, user preference helpers, and activity fetch in parallel.- The resulting snapshot plus
getSubscriptionSnapshotfeedDashboardClientviainitialContent. The layout keeps that client instance alive while the user navigates to other sections. - Subsequent client requests hit the cached API routes. Cache headers allow Edge nodes to serve the last response while background regeneration runs.
- Admin or ingestion workflows call cache invalidation helpers so future requests pull new data without waiting for TTL expiry.
Operational Tips
- •Inspect cache headers in the browser Network tab (
s-maxageandstale-while-revalidate) to confirm which layer handled the response. - •Use
get_cache_statusviacache-revalidation.tswhen diagnosing stale dashboards; it reports which tag groups still require refresh. - •When adjusting Supabase schemas or materialized views, refresh them with
refreshCostAggregationViewsand then invalidate the relevant tags to prevent mixed schema versions.