V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
• 请不要在回答技术问题时复制粘贴 AI 生成的内容
s1n1an
V2EX  ›  程序员

前端 Next.js + @tanstack/react-query 的 hydrate 几个问题

  •  
  •   s1n1an · 1 天前 · 725 次点击

    第一个问题,网页中的全局登录状态,就是右上角显示用户名和头像的小组件,用 <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  
       1 天前
    nextjs 有个 middleware.ts 中间件中可以从 request 中读取 cookie ,header 倒没注意
    realJamespond
        2
    realJamespond  
       1 天前
    直接用 nextauth+ useSession 不行么
    Razio
        3
    Razio  
       1 天前
    你到底是 SSG ,还是 SSR ?为啥不全走 SSR ,干嘛非要搞到前端异步。

    纯 SSG 的话,都构建完了,肯定水合完成之后,才走异步接口去变化的吧,哪有什么 Hydration Error ?
    xiaoming1992
        4
    xiaoming1992  
       1 天前
    如果你不想在服务端获取登录态,想要保持当前页的 SSG, 可以把需要登录态的组件用 NoSsr 包裹,不在服务端渲染,就不会有 Hydration Error 了。
    这样会有 CLS 问题,做个占位符或者从页面布局上减少 CLS 的影响就好了。
    但是这种方式仅适用于 小&少 部分组件,你这种只有右上角用户头像 NoSsr 没问题。但如果整个页面大半都用 NoSsr 包裹,那整个体验就很捞了。
    s1n1an
        5
    s1n1an  
    OP
       1 天前
    @heishu 用 middleware 的话也是,所有页面失去 SSG ,和 HydrationBoundary 是一样的
    s1n1an
        6
    s1n1an  
    OP
       1 天前
    @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
       1 天前
    打印日志,看到 useSession() 在服务端预渲染时,返回始终是 { data:null, isPending: true },这是对的,符合预期;
    那问题就出在 useSession() 在客户端运行,应该是客户端运行时,页面右上角的登录态渲染的比较早,触发了 useSession 并发送请求,此时页面内深层的组件还没加载出来时,然后 useSession 提前拿到返回值了,这就导致后续组件渲染时是已登录状态,与服务端未登录的状态不一致,触发 Hydration Error
    关于   ·   帮助文档   ·   自助推广系统   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   4172 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 01:01 · PVG 09:01 · LAX 18:01 · JFK 21:01
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.