前端 Next.js + @tanstack/react-query 的 hydrate 几个问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
s1n1an
V2EX    程序员

前端 Next.js + @tanstack/react-query 的 hydrate 几个问题
  •  
  •   s1n1an 47 天前 1467 次点击

    这是一个创建于 47 天前的主题,其中的信息可能已经有所发展或是发生改变。

    第一个问题,网页中的全局登录状态,就是右上角显示用户名和头像的小组件,用 <HydrationBoundary> + prefetchQuery() 提前在服务端注水然后前端 useQuery(),还是单纯前端 useQuery() 拉取?

    主要是涉及到一个 SSG 的问题,因为登录状态需要访问 headers(),这会导致全站失去 SSG ,我在想这样会不会影响性能?


    第二个问题,基于上面的不在服务端提前注水的方案,用 useQuery() 封装的用户登录态的 hooks 例如 useLoginedUser(),页面中即使是客户端组件,多个组件一起使用,也很容易出现 "Hydration Error",必须用 useMounted() 来判断是否运行于浏览器,这样才能不出错。

    我估计是服务端预渲染阶段拿不到 cookies ,始终得到未登录的结果,这就和客户端不一致了。 我的想法对吗?这个问题有啥解决方案吗?

    当然,用上面的 <HydrationBoundary> 放在根节点,再提前 prefetchQuery() 拿到登录态,这样就没问题了,但还是那个问题,这就失去 SSG 了。 而且就算局部 <HydrationBoundary> 来获取登录态,给局部组件用,但全局右上角那个登录态没有注水,运行 useQuery() 还是会导致 Hydration Error 。

    7 条回复    2025-08-27 21:37:40 +08:00
    heishu
        1
    heishu  
       47 天前
    nextjs 有个 middleware.ts 中间件中可以从 request 中读取 cookie ,header 倒没注意
    realJamespond
        2
    realJamespond  
       47 天前
    直接用 nextauth+ useSession 不行么
    Razio
        3
    Razio  
       47 天前
    你到底是 SSG ,还是 SSR ?为啥不全走 SSR ,干嘛非要搞到前端异步。

    纯 SSG 的话,都构建完了,肯定水合完成之后,才走异步接口去变化的吧,哪有什么 Hydration Error ?
    xiaoming1992
        4
    xiaoming1992  
       47 天前
    如果你不想在服务端获取登录态,想要保持当前页的 SSG, 可以把需要登录态的组件用 NoSsr 包裹,不在服务端渲染,就不会有 Hydration Error 了。
    这样会有 CLS 问题,做个占位符或者从页面布局上减少 CLS 的影响就好了。
    但是这种方式仅适用于 小&少 部分组件,你这种只有右上角用户头像 NoSsr 没问题。但如果整个页面大半都用 NoSsr 包裹,那整个体验就很捞了。
    s1n1an
        5
    s1n1an  
    OP
       47 天前
    @heishu 用 middleware 的话也是,所有页面失去 SSG ,和 HydrationBoundary 是一样的
    s1n1an
        6
    s1n1an  
    div class="badge op">OP
       47 天前
    @Razio
    全部 SSR 肯定没问题,也不会有 Hydration Error ;哪怕用根 HydrationBoundary 把登录状态提前 prefetch ,也不会报错的,我上面就说了呀。

    我主要是希望尽可能多静态渲染,只有非 SSR 不可的页面再 SSR ;
    右上角那个登录状态,所有页面都要用,所以要是这里用 SSR 来读 cookies 的话所有页面都是 SSR 了,不符合需求;
    所以我之前和 4 楼说的一样,右上角那个登录态做成客户端组件,用 useQuery 去查登录态,请求返回之前有个 loading 的样式占位,这一切正常。

    现在问题,只要客户端组件多处用到 useQuery (或者 better-auth 的 useSession ,也一样) 来查询登录态,居然能出现 Hydration Error ,这是客户端组件啊,刷新 N 次就能复现一次;如果是上级 layout.tsx 用 SSR 把数据注入 <HydrationBoundary>,那触发的更频繁了,基本刷新 2 次就复现一次。
    当然是登录时才有这 Error ,退出登录就好了,刷新 100 次也没 Error 。

    对登录 hook 的返回值打 log 发现,如果是这个顺序:A undefined 、B undefined 、C undefined 、A 已登录、B 已登录、C 已登录,这样就正常;
    报错的时候是这样:A undefined 、A 已登录、B 已登录、C 已登录,或者是:A undefined 、B undefined 、A 已登录、B 已登录、C 已登录,这样就报错了;
    难不成 useQuery() 在预渲染的时候返回值不稳定?如果变的时机提前了,例如在 B 代码运行前就变了或者在 C 代码运行前就变了,就导致后续的若干个组件出问题?

    请求数据也都是先根据 isPending 再根据 data 来读取的,想不明白怎么还能这样;本来我以为是 better-auth 的 isPending 有问题,查了一下,确实之前有这个 bug ,但是五月份已经修复并 merged 了;也试了一下不根据 isPending 也是有问题。

    也像我说的一样,如果用 useMounted() 来提前区分一次,这样也是 100% 不报错的;
    所以我觉得是 Next.js 在预渲染的时候出问题了?毕竟就算是客户端组件也会预渲染。
    s1n1an
        7
    s1n1an  
    OP
       47 天前
    打印日志,看到 useSession() 在服务端预渲染时,返回始终是 { data:null, isPending: true },这是对的,符合预期;
    那问题就出在 useSession() 在客户端运行,应该是客户端运行时,页面右上角的登录态渲染的比较早,触发了 useSession 并发送请求,此时页面内深层的组件还没加载出来时,然后 useSession 提前拿到返回值了,这就导致后续组件渲染时是已登录状态,与服务端未登录的状态不一致,触发 Hydration Error
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     972 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 22:39 PVG 06:39 LAX 15:39 JFK 18:39
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86