Next.js 13.4.12服务端页面使用Zustand本地存储的问题
解决方案
核心问题拆解
- 服务端环境没有
localStorage,直接使用带persist的Zustand Store会报错 - Next.js 13服务端组件不能调用任何React Hooks(包括Zustand的
useStore),所以你之前在服务端页面里调用useServerStore的写法本身就不合法
步骤1:改造Zustand Store适配服务端环境
修改Store代码,通过判断环境切换存储方式,服务端使用空存储避免报错:
import create from "zustand"; import { persist, createJSONStorage } from "zustand/middleware"; // 判断是否处于浏览器环境 const isBrowser = typeof window !== "undefined"; const useProjectStore = create( persist( (set) => ({ selectedProject: null, setSelectedProject: (project) => set({ selectedProject: project }), }), { name: "food-storage", // 服务端用空存储,浏览器用localStorage storage: createJSONStorage(() => isBrowser ? localStorage : { getItem: () => null, setItem: () => {}, removeItem: () => {}, }), } ) ); export default useProjectStore;
步骤2:服务端页面获取选中项目ID的正确方式
服务端无法直接读取客户端的localStorage,必须通过客户端主动传递状态的方式获取projectId,推荐两种方案:
方案一:URL搜索参数(推荐)
- 客户端组件更新URL参数:
当用户选择项目时,同步更新URL的projectId参数:
"use client"; import useProjectStore from "@/stores/currentProject-store"; import { useRouter, useSearchParams } from "next/navigation"; const ProjectSelector = () => { const { selectedProject, setSelectedProject } = useProjectStore(); const router = useRouter(); const searchParams = useSearchParams(); const handleSelectProject = (project) => { setSelectedProject(project); // 更新URL的query参数 const newParams = new URLSearchParams(searchParams); newParams.set("projectId", project.id); router.push(`/dashboard/images?${newParams.toString()}`); }; // 渲染项目选择UI... }; export default ProjectSelector;
- 服务端页面读取参数并查询:
import { prisma } from "@/lib/prismaDb"; import ImageTable from "@/app/dashboard/components/imageTable"; // 服务端组件接收searchParams参数 const ImagesPage = async ({ searchParams }) => { const projectId = searchParams?.projectId; // 根据projectId查询数据,无ID则返回空列表 const imageList = projectId ? await prisma.facebookImage.findMany({ where: { projectId }, }) : []; return ( <div> <ImageTable imageList={imageList} /> </div> ); }; export default ImagesPage;
方案二:Cookie存储
- 客户端组件写入Cookie:
"use client"; import useProjectStore from "@/stores/currentProject-store"; const ProjectSelector = () => { const { selectedProject, setSelectedProject } = useProjectStore(); const handleSelectProject = (project) => { setSelectedProject(project); // 写入Cookie(有效期1天) document.cookie = `selectedProjectId=${project.id}; path=/; max-age=86400`; }; // 渲染项目选择UI... }; export default ProjectSelector;
- 服务端页面读取Cookie:
import { prisma } from "@/lib/prismaDb"; import ImageTable from "@/app/dashboard/components/imageTable"; import { cookies } from "next/headers"; const ImagesPage = async () => { const cookieStore = cookies(); const projectId = cookieStore.get("selectedProjectId")?.value; const imageList = projectId ? await prisma.facebookImage.findMany({ where: { projectId }, }) : []; return ( <div> <ImageTable imageList={imageList} /> </div> ); }; export default ImagesPage;
关键注意事项
- 服务端组件绝对不能调用任何React Hooks,所有数据必须通过URL参数、Cookie、请求头等方式传递
- 改造后的Store在服务端初始化时不会依赖
localStorage,避免报错
内容的提问来源于stack exchange,提问作者Thomas
相关产品推荐
相关产品推荐

