perf: fetch session and comments in parallel, pass current user to comment components instead of useSession

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
jakeandClaude Sonnet 5.5 committed 2026-10-03 08:48:15 -04:00
1 parent 1d8b8358ca
commit 6527c7081c
6 files changed
+56 -36

No files matched your search

+11 -7
View File
@@ -22,8 +22,7 @@ import {
} from "@/components/ui/dropdown-menu"; } from "@/components/ui/dropdown-menu";
import { Spinner } from "@/components/ui/spinner"; import { Spinner } from "@/components/ui/spinner";
import { toast } from "@/components/ui/toast"; import { toast } from "@/components/ui/toast";
import { useSession } from "@/lib/auth-client"; import type { CommentUser, CommentWithUser } from "@/lib/data/comments";
import type { CommentWithUser } from "@/lib/data/comments";
import { deleteComment } from "@/lib/server/comments"; import { deleteComment } from "@/lib/server/comments";
import { EditCommentForm, ReplyForm } from "./comment-form"; import { EditCommentForm, ReplyForm } from "./comment-form";
@@ -35,12 +34,16 @@ type ActionMode =
| { type: "confirming-delete" } | { type: "confirming-delete" }
| { type: "deleting" }; | { type: "deleting" };
const CommentActions = ({ comment }: { comment: CommentWithUser }) => { const CommentActions = ({
comment,
currentUser,
}: {
comment: CommentWithUser;
currentUser: CommentUser | null;
}) => {
const [mode, setMode] = useState<ActionMode>({ type: "idle" }); const [mode, setMode] = useState<ActionMode>({ type: "idle" });
const { data: session } = useSession(); if (!currentUser) return null;
if (!session) return null;
const handleDelete = async () => { const handleDelete = async () => {
setMode({ type: "deleting" }); setMode({ type: "deleting" });
@@ -87,7 +90,7 @@ const CommentActions = ({ comment }: { comment: CommentWithUser }) => {
Reply Reply
</Button> </Button>
{session.user.id === comment.user.id && ( {currentUser.id === comment.user.id && (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger render={<Button variant="outline" size="sm" />}> <DropdownMenuTrigger render={<Button variant="outline" size="sm" />}>
<IconDots /> <IconDots />
@@ -117,6 +120,7 @@ const CommentActions = ({ comment }: { comment: CommentWithUser }) => {
<ReplyForm <ReplyForm
slug={comment.pageSlug} slug={comment.pageSlug}
parentId={comment.id} parentId={comment.id}
currentUser={currentUser}
onCancel={() => setMode({ type: "idle" })} onCancel={() => setMode({ type: "idle" })}
onSuccess={() => setMode({ type: "idle" })} onSuccess={() => setMode({ type: "idle" })}
/> />
+12 -16
View File
@@ -8,7 +8,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover
import { Spinner } from "@/components/ui/spinner"; import { Spinner } from "@/components/ui/spinner";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { toast } from "@/components/ui/toast"; import { toast } from "@/components/ui/toast";
import { useSession } from "@/lib/auth-client"; import type { CommentUser } from "@/lib/data/comments";
import { createComment, updateComment } from "@/lib/server/comments"; import { createComment, updateComment } from "@/lib/server/comments";
import { CommentAvatar } from "./comment-avatar"; import { CommentAvatar } from "./comment-avatar";
@@ -94,18 +94,12 @@ const CommentTextarea = ({
/> />
); );
// Current user's avatar (uses session) // Current user's avatar
const CurrentUserAvatar = () => { const CurrentUserAvatar = ({ user }: { user: CommentUser }) => (
const { data: session } = useSession(); <div className="shrink-0">
<CommentAvatar name={user.name} image={user.image} />
if (!session?.user) return null; </div>
);
return (
<div className="shrink-0">
<CommentAvatar name={session.user.name} image={session.user.image} />
</div>
);
};
// Submit button with pending state // Submit button with pending state
const SubmitButton = ({ const SubmitButton = ({
@@ -196,7 +190,7 @@ const MarkdownHelp = () => (
); );
// New comment form - for creating top-level comments // New comment form - for creating top-level comments
const NewCommentForm = ({ slug }: { slug: string }) => { const NewCommentForm = ({ slug, currentUser }: { slug: string; currentUser: CommentUser }) => {
const { content, setContent, isPending, startTransition } = useCommentFormState(); const { content, setContent, isPending, startTransition } = useCommentFormState();
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
@@ -225,7 +219,7 @@ const NewCommentForm = ({ slug }: { slug: string }) => {
return ( return (
<form onSubmit={handleSubmit} className="space-y-4" data-intent="create"> <form onSubmit={handleSubmit} className="space-y-4" data-intent="create">
<div className="flex gap-4"> <div className="flex gap-4">
<CurrentUserAvatar /> <CurrentUserAvatar user={currentUser} />
<div className="min-w-0 flex-1 space-y-4"> <div className="min-w-0 flex-1 space-y-4">
<CommentTextarea <CommentTextarea
@@ -257,11 +251,13 @@ const NewCommentForm = ({ slug }: { slug: string }) => {
const ReplyForm = ({ const ReplyForm = ({
slug, slug,
parentId, parentId,
currentUser,
onCancel, onCancel,
onSuccess, onSuccess,
}: { }: {
slug: string; slug: string;
parentId: string; parentId: string;
currentUser: CommentUser;
onCancel: () => void; onCancel: () => void;
onSuccess?: () => void; onSuccess?: () => void;
}) => { }) => {
@@ -294,7 +290,7 @@ const ReplyForm = ({
return ( return (
<form onSubmit={handleSubmit} className="space-y-4" data-intent="create"> <form onSubmit={handleSubmit} className="space-y-4" data-intent="create">
<div className="flex gap-4"> <div className="flex gap-4">
<CurrentUserAvatar /> <CurrentUserAvatar user={currentUser} />
<div className="min-w-0 flex-1 space-y-4"> <div className="min-w-0 flex-1 space-y-4">
<CommentTextarea <CommentTextarea
+9 -3
View File
@@ -2,7 +2,7 @@ import Link from "next/link";
import Markdown from "react-markdown"; import Markdown from "react-markdown";
import { RelativeTime } from "@/components/relative-time"; import { RelativeTime } from "@/components/relative-time";
import type { CommentWithUser } from "@/lib/data/comments"; import type { CommentUser, CommentWithUser } from "@/lib/data/comments";
import { rehypeExternalLinks } from "@/lib/rehype"; import { rehypeExternalLinks } from "@/lib/rehype";
import { remarkGfm, remarkSmartypants } from "@/lib/remark"; import { remarkGfm, remarkSmartypants } from "@/lib/remark";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
@@ -10,7 +10,13 @@ import { cn } from "@/lib/utils";
import { CommentActions } from "./comment-actions"; import { CommentActions } from "./comment-actions";
import { CommentAvatar } from "./comment-avatar"; import { CommentAvatar } from "./comment-avatar";
const CommentSingle = ({ comment }: { comment: CommentWithUser }) => { const CommentSingle = ({
comment,
currentUser,
}: {
comment: CommentWithUser;
currentUser: CommentUser | null;
}) => {
const divId = `comment-${comment.id.substring(0, 8)}`; const divId = `comment-${comment.id.substring(0, 8)}`;
return ( return (
@@ -62,7 +68,7 @@ const CommentSingle = ({ comment }: { comment: CommentWithUser }) => {
</Markdown> </Markdown>
</div> </div>
<CommentActions comment={comment} /> <CommentActions comment={comment} currentUser={currentUser} />
</div> </div>
</div> </div>
</div> </div>
+5 -2
View File
@@ -1,4 +1,4 @@
import type { CommentWithUser } from "@/lib/data/comments"; import type { CommentUser, CommentWithUser } from "@/lib/data/comments";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import { CommentSingle } from "./comment-single"; import { CommentSingle } from "./comment-single";
@@ -10,15 +10,17 @@ const CommentThread = ({
comment, comment,
replies, replies,
allComments, allComments,
currentUser,
level = 0, level = 0,
}: { }: {
comment: CommentWithUser; comment: CommentWithUser;
replies: CommentWithUser[]; replies: CommentWithUser[];
allComments: Record<string, CommentWithUser[]>; allComments: Record<string, CommentWithUser[]>;
currentUser: CommentUser | null;
level?: number; level?: number;
}) => ( }) => (
<> <>
<CommentSingle comment={comment} /> <CommentSingle comment={comment} currentUser={currentUser} />
{replies.length > 0 && ( {replies.length > 0 && (
<div className={cn("mt-6 space-y-6", level < MAX_NESTING_LEVEL && "ml-6 border-l-2 pl-6")}> <div className={cn("mt-6 space-y-6", level < MAX_NESTING_LEVEL && "ml-6 border-l-2 pl-6")}>
@@ -28,6 +30,7 @@ const CommentThread = ({
comment={reply} comment={reply}
replies={allComments[reply.id] || []} replies={allComments[reply.id] || []}
allComments={allComments} allComments={allComments}
currentUser={currentUser}
level={level + 1} level={level + 1}
/> />
))} ))}
+15 -7
View File
@@ -1,18 +1,25 @@
import { headers } from "next/headers"; import { headers } from "next/headers";
import { auth } from "@/lib/auth"; import { auth } from "@/lib/auth";
import { type CommentWithUser, getComments } from "@/lib/data/comments"; import { type CommentUser, type CommentWithUser, getComments } from "@/lib/data/comments";
import { NewCommentForm } from "./comment-form"; import { NewCommentForm } from "./comment-form";
import { CommentThread } from "./comment-thread"; import { CommentThread } from "./comment-thread";
import { SignIn } from "./sign-in"; import { SignIn } from "./sign-in";
const Comments = async ({ slug }: { slug: string }) => { const Comments = async ({ slug }: { slug: string }) => {
const session = await auth.api.getSession({ // independent queries -- run them in parallel
headers: await headers(), const [session, comments] = await Promise.all([
}); auth.api.getSession({
headers: await headers(),
}),
getComments(slug),
]);
const comments = await getComments(slug); // only pass the public fields down to client components
const currentUser: CommentUser | null = session
? { id: session.user.id, name: session.user.name, image: session.user.image ?? null }
: null;
const commentsByParentId = comments.reduce( const commentsByParentId = comments.reduce(
(acc, comment) => { (acc, comment) => {
@@ -30,8 +37,8 @@ const Comments = async ({ slug }: { slug: string }) => {
return ( return (
<> <>
{session ? ( {currentUser ? (
<NewCommentForm slug={slug} /> <NewCommentForm slug={slug} currentUser={currentUser} />
) : ( ) : (
<div className="flex flex-col items-center justify-center gap-y-4 rounded-lg bg-muted/40 p-6"> <div className="flex flex-col items-center justify-center gap-y-4 rounded-lg bg-muted/40 p-6">
<p className="text-center font-medium">Join the discussion by signing in:</p> <p className="text-center font-medium">Join the discussion by signing in:</p>
@@ -47,6 +54,7 @@ const Comments = async ({ slug }: { slug: string }) => {
comment={comment} comment={comment}
replies={commentsByParentId[comment.id] || []} replies={commentsByParentId[comment.id] || []}
allComments={commentsByParentId} allComments={commentsByParentId}
currentUser={currentUser}
/> />
))} ))}
</div> </div>
+4 -1
View File
@@ -4,8 +4,11 @@ import { desc, eq, sql } from "drizzle-orm";
import { db } from "@/lib/db"; import { db } from "@/lib/db";
import * as schema from "@/lib/db/schema"; import * as schema from "@/lib/db/schema";
/** The public profile fields of a commenter (also used for the signed-in user in comment UI) */
export type CommentUser = Pick<typeof schema.user.$inferSelect, "id" | "name" | "image">;
export type CommentWithUser = typeof schema.comment.$inferSelect & { export type CommentWithUser = typeof schema.comment.$inferSelect & {
user: Pick<typeof schema.user.$inferSelect, "id" | "name" | "image">; user: CommentUser;
}; };
/** /**