文章
nextjs导航栏用户信息管理
主要是为了解决登录用户信息页面显示的时候会先加载Loading然后显示用户信息问题
1、server端获取用户信息
"use server";
import { fetchWithAuth } from "@/lib/server-fetch";
export async function fetchCurrentUser() {
try {
const res = await fetchWithAuth("/user/profile", { method: "GET" });
const { data } = await res.json();
return data ?? null; // 直接返回 UserInfo
} catch (err) {
console.error("fetchCurrentUser error:", err);
return null;
}
}2、layout server端渲染页面前加载用户信息
import { ReactNode } from "react";
import {
SidebarInset,
SidebarProvider,
SidebarTrigger,
} from "@/components/ui/sidebar";
import { fetchCurrentUser } from "@/lib/user";
import { AppSidebar } from "@/components/app-sidebar";
import { NavUser } from "@/components/nav-user";
import { ModeToggle } from "@/components/mode-toggle";
import { Separator } from "@/components/ui/separator";
export default async function DashboardLayout({ children }: { children: ReactNode }) {
// 服务端获取当前登录用户信息
const user = await fetchCurrentUser();
return (
<SidebarProvider>
<AppSidebar />
<SidebarInset>
<header className="flex h-16 shrink-0 items-center gap-2">
<div className="flex items-center gap-2 px-4">
<SidebarTrigger className="-ml-1" />
<Separator
orientation="vertical"
className="mr-2 data-[orientation=vertical]:h-4"
/>
</div>
<div className="ml-auto px-4">
<div className="flex items-center gap-2 text-sm">
<ModeToggle />
{user && <NavUser initialUser={user} />}
</div>
</div>
</header>
<main className="flex flex-1 flex-col gap-4 p-4 pt-0">
{children}
</main>
</SidebarInset>
</SidebarProvider>
);
}
3、导航用户组件 接收server端用户信息
"use client"
import {
BadgeCheck,
Bell,
ChevronsUpDown,
CreditCard,
LogOut,
} from "lucide-react"
import {
Avatar,
AvatarFallback,
AvatarImage,
} from "@/components/ui/avatar"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
} from "@/components/ui/sidebar"
import { UserInfo } from "@/app/(public)/login/_libs/use-auth-store"
import { useRouter } from "next/navigation"
import { logout } from "@/app/(public)/login/_services/auth"
import { alert } from "@/lib/use-global-store"
import Link from "next/link"
import { useCurrentUser } from "@/app/(protected)/dashboard/settings/user/services/use-user-queries"
export function NavUser({ initialUser }: { initialUser?: UserInfo }) {
const { data: currentUser } = useCurrentUser();
const user = currentUser || initialUser;
const router = useRouter();
const handleLogout = async () => {
await logout();
router.replace("/login");
}
if (!user) {
return null;
}
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
>
<Avatar className="h-8 w-8 rounded-lg">
<AvatarImage src={user.avatar} alt={user.fullName} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
<div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium">{user.fullName}</span>
<span className="truncate text-xs">{user.email}</span>
</div>
<ChevronsUpDown className="ml-auto size-4" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
side="bottom"
align="end"
sideOffset={4}
>
<DropdownMenuGroup>
<DropdownMenuItem asChild>
<Link href="/dashboard/settings/user">
<BadgeCheck />
个人中心
</Link>
</DropdownMenuItem>
<DropdownMenuItem>
<CreditCard />
Billing
</DropdownMenuItem>
<DropdownMenuItem>
<Bell />
Notifications
</DropdownMenuItem>
</DropdownMenuGroup>
<DropdownMenuSeparator />
<DropdownMenuItem variant="destructive" onClick={() => alert({
onConfirm: handleLogout,
title: "警告!",
description: "确定退出?",
confirmLabel: "确定",
cancelLabel: "取消"
})}>
<LogOut />
退出
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
)
}
这里的useCurrentUser来自React Query 缓存,支持不刷页面更新数据
import { fetchCurrentUser } from "@/lib/user";
import { useQuery } from "@tanstack/react-query";
export const useCurrentUser = () =>
useQuery({
queryKey: ["currentUser"],
queryFn: fetchCurrentUser,
staleTime: 1000 * 60, // 缓存 1 分钟
});说明:
- const user = currentUser || initialUser; 在获取缓存时currentUser无值使用server端user传值
- 也就是在空窗期有值可以直接渲染到页面,不使用Loading效果