perf(frontend): stream sidebar via Suspense — feed renders immediately; sidebar loads async
This commit is contained in:
@@ -1,12 +1,6 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
import {
|
import { getFeed, getMe, Me } from "@/lib/api";
|
||||||
getFeed,
|
|
||||||
getFriends,
|
|
||||||
getMe,
|
|
||||||
getTopFriends,
|
|
||||||
Me,
|
|
||||||
} from "@/lib/api";
|
|
||||||
import { ThoughtForm } from "@/components/thought-form";
|
import { ThoughtForm } from "@/components/thought-form";
|
||||||
import { EmptyState } from "@/components/empty-state";
|
import { EmptyState } from "@/components/empty-state";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -16,9 +10,10 @@ import { ThoughtThread } from "@/components/thought-thread";
|
|||||||
import { buildThoughtThreads } from "@/lib/utils";
|
import { buildThoughtThreads } from "@/lib/utils";
|
||||||
import { TopFriends } from "@/components/top-friends";
|
import { TopFriends } from "@/components/top-friends";
|
||||||
import { UsersCount } from "@/components/users-count";
|
import { UsersCount } from "@/components/users-count";
|
||||||
|
|
||||||
import { PaginationNav } from "@/components/pagination-nav";
|
import { PaginationNav } from "@/components/pagination-nav";
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
import { Suspense } from "react";
|
||||||
|
import { ProfileSkeleton, TagsSkeleton, CountSkeleton } from "@/components/loading-skeleton";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Home",
|
title: "Home",
|
||||||
@@ -61,18 +56,26 @@ async function FeedPage({
|
|||||||
const { items: allThoughts, totalPages } = feedData!;
|
const { items: allThoughts, totalPages } = feedData!;
|
||||||
const thoughtThreads = buildThoughtThreads(allThoughts);
|
const thoughtThreads = buildThoughtThreads(allThoughts);
|
||||||
|
|
||||||
const friends = (await getFriends(token)).users.map((user) => user.username);
|
const sidebar = (
|
||||||
const topFriendsData = me
|
<>
|
||||||
? await getTopFriends(me.username, token).catch(() => ({ topFriends: [] }))
|
<Suspense fallback={<ProfileSkeleton />}>
|
||||||
: { topFriends: [] };
|
<TopFriends username={me.username} />
|
||||||
const shouldDisplayTopFriends = topFriendsData.topFriends.length > 0;
|
</Suspense>
|
||||||
|
<Suspense fallback={<TagsSkeleton />}>
|
||||||
|
<PopularTags />
|
||||||
|
</Suspense>
|
||||||
|
<Suspense fallback={<CountSkeleton />}>
|
||||||
|
<UsersCount />
|
||||||
|
</Suspense>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto max-w-6xl p-4 sm:p-6">
|
<div className="container mx-auto max-w-6xl p-4 sm:p-6">
|
||||||
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
||||||
<aside className="hidden lg:block lg:col-span-1">
|
<aside className="hidden lg:block lg:col-span-1">
|
||||||
<div className="sticky top-20 space-y-6 glass-effect glossy-effect bottom rounded-md p-4">
|
<div className="sticky top-20 space-y-6 glass-effect glossy-effect bottom rounded-md p-4">
|
||||||
<h2 className="text-lg font-semibold">Filters & Sorting</h2>
|
<h2 className="text-lg font-semibold">Filters & Sorting</h2>
|
||||||
<p className="text-sm text-muted-foreground">Coming soon...</p>
|
<p className="text-sm text-muted-foreground">Coming soon...</p>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -84,14 +87,7 @@ async function FeedPage({
|
|||||||
<ThoughtForm />
|
<ThoughtForm />
|
||||||
|
|
||||||
<div className="block lg:hidden space-y-6">
|
<div className="block lg:hidden space-y-6">
|
||||||
<PopularTags />
|
{sidebar}
|
||||||
{shouldDisplayTopFriends && (
|
|
||||||
<TopFriends mode="top-friends" usernames={topFriendsData.topFriends} />
|
|
||||||
)}
|
|
||||||
{!shouldDisplayTopFriends && token && friends.length > 0 && (
|
|
||||||
<TopFriends mode="friends" usernames={friends || []} />
|
|
||||||
)}
|
|
||||||
<UsersCount />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
@@ -115,14 +111,7 @@ async function FeedPage({
|
|||||||
|
|
||||||
<aside className="hidden lg:block lg:col-span-1">
|
<aside className="hidden lg:block lg:col-span-1">
|
||||||
<div className="sticky top-20 space-y-6">
|
<div className="sticky top-20 space-y-6">
|
||||||
<PopularTags />
|
{sidebar}
|
||||||
{shouldDisplayTopFriends && (
|
|
||||||
<TopFriends mode="top-friends" usernames={topFriendsData.topFriends} />
|
|
||||||
)}
|
|
||||||
{!shouldDisplayTopFriends && token && friends.length > 0 && (
|
|
||||||
<TopFriends mode="friends" usernames={friends || []} />
|
|
||||||
)}
|
|
||||||
<UsersCount />
|
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
getMe,
|
getMe,
|
||||||
getRemoteFollowers,
|
getRemoteFollowers,
|
||||||
getRemoteFollowing,
|
getRemoteFollowing,
|
||||||
getTopFriends,
|
|
||||||
getUserProfile,
|
getUserProfile,
|
||||||
getUserThoughts,
|
getUserThoughts,
|
||||||
Me,
|
Me,
|
||||||
@@ -52,6 +51,8 @@ import { notFound } from "next/navigation";
|
|||||||
import { cookies } from "next/headers";
|
import { cookies } from "next/headers";
|
||||||
import { FollowButton } from "@/components/follow-button";
|
import { FollowButton } from "@/components/follow-button";
|
||||||
import { TopFriends } from "@/components/top-friends";
|
import { TopFriends } from "@/components/top-friends";
|
||||||
|
import { Suspense } from "react";
|
||||||
|
import { ProfileSkeleton } from "@/components/loading-skeleton";
|
||||||
import { buildThoughtThreads } from "@/lib/utils";
|
import { buildThoughtThreads } from "@/lib/utils";
|
||||||
import { ThoughtThread } from "@/components/thought-thread";
|
import { ThoughtThread } from "@/components/thought-thread";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -127,15 +128,6 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
|
|||||||
const fediverseHandle =
|
const fediverseHandle =
|
||||||
user.local && apiDomain ? `@${user.username}@${apiDomain}` : null;
|
user.local && apiDomain ? `@${user.username}@${apiDomain}` : null;
|
||||||
|
|
||||||
// Show who the profile owner follows (uses the already-fetched followingResult).
|
|
||||||
const friends =
|
|
||||||
followingResult.status === "fulfilled"
|
|
||||||
? followingResult.value.items.map((u) => u.username)
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const topFriendsData = await getTopFriends(username, token).catch(() => ({ topFriends: [] }));
|
|
||||||
const shouldDisplayTopFriends = topFriendsData.topFriends.length > 0;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id={`profile-page-${user.username}`}>
|
<div id={`profile-page-${user.username}`}>
|
||||||
{user.customCss && (
|
{user.customCss && (
|
||||||
@@ -252,10 +244,9 @@ export default async function ProfilePage({ params }: ProfilePageProps) {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{shouldDisplayTopFriends && (
|
<Suspense fallback={<ProfileSkeleton />}>
|
||||||
<TopFriends mode="top-friends" usernames={topFriendsData.topFriends} />
|
<TopFriends username={user.username} />
|
||||||
)}
|
</Suspense>
|
||||||
{token && <TopFriends mode="friends" usernames={friends || []} />}
|
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user