适用范围

整理自 Yggdrasil 在 2026 年 10 月迁移到 Leptos 0.8.22、使用 cargo-leptos 0.3.11 路由分包时的排障记录。这是特定版本下的经验,不代表后续版本仍存在相同缺陷;本文未重新执行当时的实验。

资源属于路由数据阶段,不属于视图构建阶段

在该项目的懒路由中,SSR 可能多次调用 view()。把资源创建放进去,会重复分配资源,并可能让服务端与客户端的资源编号错位,表现为水合挂起。

项目采用的边界是:在 LazyRoute::data() 中创建资源,view() 只消费数据。需要减小入口包时,用类型擦除的 JSON 数据桥接与懒加载取数函数,而不是移动资源的创建时机。

遇到“HTML 已显示,但页面一直不能交互”,不能只检查网络是否完成;还要检查两端是否创建了相同顺序、相同数量的响应式资源。

数据决定状态码时,注意流式响应的提交时机

InOrder 模式开始发送响应后,后续异步结果无法再可靠地修改已经发出的 HTTP 状态或 head。

如果 404、标题或 Meta 依赖异步数据,项目使用 SsrMode::Async。不能只看页面是否画出了“找不到”:还应检查实际响应是否为 404。外层 Suspense 也会影响“错误会同步返回”的假设。

SharedValue 必须在水合期间建立

服务端请求上下文通过 SharedValue 传到浏览器时,项目曾把创建位置放在布局中;布局可能在水合完成后才挂载,此时浏览器 initializer 会运行,得到默认空值。

修复方式是在 App 根部、建立 Meta 上下文后创建并提供共享上下文。分享组件只读取同一份上下文,不各自重建。

异步任务不能假定视图仍然存活

等待定时器、网络请求或测量任务后,原来的路由 owner 可能已销毁。历史排查中,延迟回调读取已 disposed 的 signal 会触发 WASM panic,浏览器表面只显示 RuntimeError: unreachable。

延迟读取使用 try_get_untracked 等可失败访问;页面已经离开时,不继续使用旧视图状态。需要调试时,先取得 Rust panic 原文,不要把 unreachable 本身当成根因。

服务端与浏览器的编译约定必须一致

该项目使用 erase_components;历史实验发现,只在一端启用会导致页面静默停在未水合状态。检查构建时,既要看 WASM 配置,也要看服务端 RUSTFLAGS;Docker 中重新设置 RUSTFLAGS 时尤其容易漏掉。

验证清单

  • 直开页面后确认真实交互可用,而不只是 HTML 可见。
  • 对数据不存在的页面检查 HTTP 状态。
  • 页面快速离开后,观察延迟任务是否读取已销毁状态。
  • 懒路由水合尚未完成时点击导航,确认不会触发水合不匹配。
  • 重建后确认浏览器实际加载了新哈希的 WASM;本项目的开发增量构建曾保留旧入口引用,需重启 make dev 后验证。

来源

Yggdrasil 2026-10-27 排障记录;相关修复包括 SharedValue 上下文迁移(fed21337)和数据相关 404 处理(42edc7df)。提交标识用于追溯历史,不表示本文核验了当前部署版本。