From c08b860b7de10d78aebf0204d5a9719f56d6287f Mon Sep 17 00:00:00 2001 From: babblebey Date: Tue, 14 Jul 2026 18:44:31 +0100 Subject: [PATCH 1/9] feat(performance): enable prefetch by adding loading skeletons for gallery, app route, members, notifications, profile, and settings --- src/app/(app)/gallery/loading.tsx | 27 ++++++++++++++ src/app/(app)/loading.tsx | 29 +++++++++++++++ src/app/(app)/members/loading.tsx | 46 +++++++++++++++++++++++ src/app/(app)/notifications/loading.tsx | 31 ++++++++++++++++ src/app/(app)/profile/loading.tsx | 47 ++++++++++++++++++++++++ src/app/(app)/settings/layout.tsx | 7 ++++ src/app/(app)/settings/loading.tsx | 41 +++++++++++++++++++++ src/components/nav/desktop-sidebar.tsx | 19 +++++++--- src/components/nav/mobile-bottom-nav.tsx | 13 ++++++- src/components/nav/mobile-header.tsx | 18 +++++++-- 10 files changed, 268 insertions(+), 10 deletions(-) create mode 100644 src/app/(app)/gallery/loading.tsx create mode 100644 src/app/(app)/loading.tsx create mode 100644 src/app/(app)/members/loading.tsx create mode 100644 src/app/(app)/notifications/loading.tsx create mode 100644 src/app/(app)/profile/loading.tsx create mode 100644 src/app/(app)/settings/loading.tsx diff --git a/src/app/(app)/gallery/loading.tsx b/src/app/(app)/gallery/loading.tsx new file mode 100644 index 0000000..2e77bd7 --- /dev/null +++ b/src/app/(app)/gallery/loading.tsx @@ -0,0 +1,27 @@ +import { Skeleton } from "~/components/ui/skeleton"; + +export default function GalleryLoading() { + return ( +
+
+ {/* Header skeleton */} +
+ + +
+ + {/* Gallery grid skeleton */} +
+
+ {Array.from({ length: 8 }).map((_, index) => ( + + ))} +
+
+
+
+ ); +} diff --git a/src/app/(app)/loading.tsx b/src/app/(app)/loading.tsx new file mode 100644 index 0000000..a2f51b7 --- /dev/null +++ b/src/app/(app)/loading.tsx @@ -0,0 +1,29 @@ +import { Skeleton } from "~/components/ui/skeleton"; + +export default function AppRouteLoading() { + return ( +
+
+ + +
+ +
+ {Array.from({ length: 3 }).map((_, index) => ( +
+
+ +
+ + +
+
+ + + +
+ ))} +
+
+ ); +} diff --git a/src/app/(app)/members/loading.tsx b/src/app/(app)/members/loading.tsx new file mode 100644 index 0000000..5c8ba56 --- /dev/null +++ b/src/app/(app)/members/loading.tsx @@ -0,0 +1,46 @@ +import { Skeleton } from "~/components/ui/skeleton"; + +export default function MembersLoading() { + return ( +
+ {/* Header skeleton */} +
+
+ + +
+ +
+ + {/* Filters and search skeleton */} +
+
+
+ {Array.from({ length: 3 }).map((_, i) => ( + + ))} +
+ +
+ + {/* Grid skeleton */} +
+ {Array.from({ length: 6 }).map((_, index) => ( +
+
+ +
+
+ + +
+ +
+
+
+ ))} +
+
+
+ ); +} diff --git a/src/app/(app)/notifications/loading.tsx b/src/app/(app)/notifications/loading.tsx new file mode 100644 index 0000000..21dd2bc --- /dev/null +++ b/src/app/(app)/notifications/loading.tsx @@ -0,0 +1,31 @@ +import { Skeleton } from "~/components/ui/skeleton"; + +export default function NotificationsLoading() { + return ( +
+ {/* Filters skeleton */} +
+ {Array.from({ length: 6 }).map((_, i) => ( + + ))} +
+ + {/* Notifications list skeleton */} +
+ {Array.from({ length: 6 }).map((_, index) => ( +
+ +
+ + + +
+
+ ))} +
+
+ ); +} diff --git a/src/app/(app)/profile/loading.tsx b/src/app/(app)/profile/loading.tsx new file mode 100644 index 0000000..5d6d550 --- /dev/null +++ b/src/app/(app)/profile/loading.tsx @@ -0,0 +1,47 @@ +import { Skeleton } from "~/components/ui/skeleton"; + +export default function ProfileLoading() { + return ( +
+ {/* Header skeleton */} +
+ +
+ + +
+
+ + {/* Edit button skeleton */} +
+ +
+ + {/* Tabs skeleton */} +
+
+ {Array.from({ length: 4 }).map((_, i) => ( + + ))} +
+ + {/* Posts list skeleton */} +
+ {Array.from({ length: 3 }).map((_, index) => ( +
+
+ +
+ + +
+
+ + +
+ ))} +
+
+
+ ); +} diff --git a/src/app/(app)/settings/layout.tsx b/src/app/(app)/settings/layout.tsx index 3c58110..4073970 100644 --- a/src/app/(app)/settings/layout.tsx +++ b/src/app/(app)/settings/layout.tsx @@ -107,6 +107,12 @@ export default function SettingsLayout({ const activeSettingsLabel = visibleSettingsNav.find((item) => item.href === activeSettingsHref)?.label ?? "Settings"; + useEffect(() => { + for (const item of visibleSettingsNav) { + void router.prefetch(item.href); + } + }, [router, visibleSettingsNav]); + function navigateToSettings(href: string) { if (href === pathname) return; beginNavigationProgress(); @@ -162,6 +168,7 @@ export default function SettingsLayout({
  • +
    + + +
    + +
    + {/* Mobile dropdown skeleton */} +
    + +
    + + {/* Settings sidebar skeleton - desktop only */} +
    +
    + {Array.from({ length: 5 }).map((_, i) => ( + + ))} +
    +
    + + {/* Content area skeleton */} +
    + +
    + {Array.from({ length: 4 }).map((_, i) => ( +
    + + +
    + ))} +
    +
    +
    + + ); +} diff --git a/src/components/nav/desktop-sidebar.tsx b/src/components/nav/desktop-sidebar.tsx index 89c86c8..75e3a99 100644 --- a/src/components/nav/desktop-sidebar.tsx +++ b/src/components/nav/desktop-sidebar.tsx @@ -1,8 +1,8 @@ "use client"; -import { useMemo } from "react"; +import { useEffect, useMemo } from "react"; import Link from "next/link"; -import { usePathname } from "next/navigation"; +import { usePathname, useRouter } from "next/navigation"; import { Bell, House, Image, Plus, Settings, Sparkles, User, Users } from "~/components/ui/icons"; import { FamilyLogotypeLockup } from "~/components/branding/family-logotype-lockup"; @@ -43,6 +43,7 @@ function isSettingsPath(pathname: string) { export function DesktopSidebar({ primaryLockup }: { primaryLockup: string }) { const pathname = usePathname(); + const router = useRouter(); const { openComposer } = useGlobalComposer(); const shouldPollUnread = !pathname.startsWith("/notifications"); @@ -94,10 +95,18 @@ export function DesktopSidebar({ primaryLockup }: { primaryLockup: string }) { const unreadLabel = formatUnreadBadgeCount(unreadCountQuery.data?.count ?? 0); + useEffect(() => { + for (const item of items) { + void router.prefetch(item.href); + } + + void router.prefetch("/settings"); + }, [items, router]); + return (