30 秒回答
服务端看不到 window.ethereum 和 localStorage,只能渲染未连接;如果客户端第一次 render 立刻使用浏览器持久化状态输出已连接,HTML 就不一致。钱包交互应放在 Client Component,浏览器 API 在 effect 或事件中访问。wagmi 可开启 ssr: true,让外部存储在挂载后水合;若要减少未连接闪烁,可使用 cookie storage,并在服务端通过 cookieToInitialState 把一致的 initialState 传给 WagmiProvider。不要为解决一个钱包组件而关闭整页 SSR。
深入回答
mismatch 的来源
Server Component 和服务端预渲染阶段没有以下浏览器能力:
window、document 和注入的 EIP-1193 Provider。
- localStorage 中的 connector/session 线索。
- 浏览器扩展实际暴露的账户和 chainId。
React hydration 会在浏览器中复用服务端 HTML。客户端的第一次渲染必须与服务端可见状态一致;如果模块顶层直接读取 window.ethereum,不仅可能在服务端报错,也可能让首次客户端输出发生变化。
组件边界
页面标题、公共链上数据和静态内容可以继续使用 Server Component。只有连接按钮、账户菜单和依赖钱包 hooks 的小范围组件需要成为 Client Component。
浏览器 API 应在以下位置使用:
- 用户点击连接等事件处理器。
- 确认组件已经挂载后的 effect。
- 已声明为 Client Component 的钱包 Provider 树中。
"use client" 代表客户端边界,不代表组件完全不会参与服务端预渲染。因此仍要保证首次输出一致。
wagmi 的 SSR 策略
wagmi 使用 localStorage、MIPD 等客户端外部存储。官方 SSR 指南建议开启 ssr: true,使这些存储在首次挂载后再水合,从而避免服务端与首次客户端输出直接冲突。
这种方式可能短暂显示未连接状态。如果产品需要更稳定的首屏,可以:
- 使用 wagmi
cookieStorage。
- 在 Next.js Server Component 中读取 cookie。
- 通过
cookieToInitialState 计算 initialState。
- 把 initialState 传入客户端的 WagmiProvider。
Cookie 只用于恢复连接状态线索,不应存放私钥,也不能替代重新读取钱包账户和链。
何时跳过 SSR
对于强依赖浏览器且没有有意义服务端占位的局部组件,可以在 Client Component 中使用动态导入并关闭该组件 SSR。Next.js 不允许在 Server Component 中直接用 ssr: false 规避边界。即使局部跳过 SSR,也应提供尺寸稳定的占位,避免布局跳动。
示例代码
export const config = createConfig({
chains: [mainnet],
ssr: true,
transports: {
[mainnet.id]: http(),
},
});
"use client";
export function WalletControls() {
const connection = useConnection();
return connection.isConnected ? <AccountMenu /> : <ConnectButton />;
}
完整应用还需要在顶层 Client Provider 中创建稳定的 config 与 QueryClient,避免每次渲染重新实例化。
常见错误
- 在模块顶层访问
window.ethereum。
- 用
typeof window !== "undefined" 让首次客户端输出与服务端完全不同。
- 给整个页面关闭 SSR,牺牲静态内容与公共数据的服务端能力。
- 认为加上
"use client" 后就不会发生服务端预渲染或 hydration。
- 使用 cookie 保存私钥、签名或把缓存地址当成仍获授权的事实。
- 用
suppressHydrationWarning 隐藏真实状态设计错误。
面试官追问
"use client" 为什么不能自动消除 hydration mismatch?
ssr: true 为什么可能带来短暂的未连接闪烁?
- cookie initialState 能证明钱包仍授权该账户吗?
- 哪些页面内容应该继续留在 Server Component?
评分标准
初级回答
- 知道服务端没有 window 和注入钱包。
- 知道浏览器钱包逻辑需要 Client Component。
- 能解释首次渲染一致性、wagmi
ssr: true 和水合时机。
- 能划分 Server/Client Component,而不是关闭整页 SSR。
高级回答
- 能设计 cookie initialState、稳定 Provider、加载占位和重新授权流程。
- 能权衡首屏一致性、隐私、缓存、静态渲染和客户端交互范围。
参考资料