NextJS 13中Zustand状态无法持久化的问题排查求助
问题分析与解决方案
核心问题原因
- Zustand状态未持久化:当前的Zustand store仅存于客户端内存,页面刷新或导航时状态会丢失,服务端组件完全无法访问客户端内存中的状态。
- 服务端组件无法直接读取客户端状态:AdminDashboard作为服务端组件,运行在服务器环境,无法获取客户端Zustand存储的用户数据。
- Root Layout跳转逻辑缺陷:页面初次加载时Zustand初始状态为null,会直接触发跳转,即便用户已登录(因状态未持久化)。
可行解决方案
1. 给Zustand添加持久化中间件
启用Zustand的persist中间件,将状态存入localStorage,确保页面刷新后状态不丢失。
修改userDataStore.ts:
import { UserData, UserStore } from "@/types"; import { create } from "zustand"; import { persist } from "zustand/middleware"; const userDataStore = create<UserStore>()( persist( (set) => ({ tenantId: null, userId: null, accessToken: null, refreshToken: null, setUserData: (userData: UserData) => set(userData), clearUserData: () => set({ tenantId: null, userId: null, accessToken: null, refreshToken: null }) }), { name: "user-data-storage", // localStorage存储键名 storage: localStorage, // 可选sessionStorage } ) ); export default userDataStore;
2. 服务端组件获取用户状态的正确方式
服务端组件需通过Cookie传递认证信息,在服务端校验用户身份:
修改AdminDashboard服务端组件:
import { cookies } from 'next/headers'; import { GetUsers } from "@/api/user"; export default async function AdminDashboard() { const cookieStore = cookies(); const accessToken = cookieStore.get('accessToken')?.value; // 服务端校验token有效性,无有效token则重定向 if (!accessToken) { return Response.redirect(new URL('/', request.url)); } const users = await GetUsers(accessToken); // 将token传入API请求 return ( <section> Hello Admin, total users: {users.length} </section> ); }
同时在登录成功后将token存入Cookie(客户端组件中操作):
// 在LoginComponent的handleSubmit中添加 const handleSubmit = async (email: string, password: string) => { setLoading(true); const loginAttemptData = await LoginApi(email, password); if (!loginAttemptData.isSuccess) { form.setErrors({ [loginAttemptData.data.incorrectField]: loginAttemptData.message, }); } if (loginAttemptData.isSuccess) { setUserData(loginAttemptData.data); // 客户端存入Cookie,配置安全属性 document.cookie = `accessToken=${loginAttemptData.data.accessToken}; path=/; max-age=86400; secure; SameSite=Lax`; document.cookie = `refreshToken=${loginAttemptData.data.refreshToken}; path=/; max-age=604800; secure; SameSite=Lax`; } setLoading(false); };
3. 修复Root Layout的认证逻辑
避免初始状态为null时误跳转,仅在非登录页且状态为空时重定向:
修改RootLayout:
'use client' import './globals.css' import { usePathname } from 'next/navigation'; import { useRouter } from 'next/navigation' import Provider from './provider' import MainShell from '@/Components/AppShell' import { useEffect, useState } from 'react'; import userDataStore from '@/Store/userData'; export default function RootLayout({ children, }: { children: React.ReactNode }) { const pathname = usePathname() const router = useRouter() const { tenantId, userId } = userDataStore() const [hidden, setHidden] = useState(true); useEffect(() => { setHidden(pathname === '/'); // 仅在非登录页且无用户状态时跳转 if (pathname !== '/' && (!tenantId || !userId)) { router.replace("/"); } }, [pathname, tenantId, userId]); return ( <html lang="en"> <body> <Provider> <MainShell hidden={hidden}> {children} </MainShell> </Provider> </body> </html> ) }
4. 客户端组件访问状态的正确方式
若将AdminDashboard改为客户端组件,需添加"use client"指令并正确订阅Zustand状态:
"use client"; import userDataStore from "@/Store/userData" import { GetUsers } from "@/api/user" import { useEffect, useState } from "react"; export default function AdminDashboard() { const { accessToken } = userDataStore(); const [users, setUsers] = useState([]); useEffect(() => { if (accessToken) { const fetchUsers = async () => { const data = await GetUsers(accessToken); setUsers(data); }; fetchUsers(); } }, [accessToken]); return ( <section> Hello Admin, total users: {users.length} </section> ) }
内容的提问来源于stack exchange,提问作者Ram Pandey
相关产品推荐
相关产品推荐

