Leptos-Axum SSR条件路由渲染问题:初始加载跳转异常
问题根源与修复方案
核心问题
你的代码存在两个关键问题,导致初始加载受保护路由时触发错误重定向:
初始渲染时状态未及时更新
你用create_effect来设置current_user,但create_effect是组件渲染完成后才执行的副作用。路由守卫的<Show>条件会在初始渲染(包括客户端hydrate和SSR)时先判断,此时global_state.current_user还是初始的None,直接触发重定向。等create_effect执行完设置正确状态时,重定向已经生效了。SSR无法访问浏览器localStorage
服务器渲染阶段根本读不到浏览器的localStorage,所以SSR时current_user默认是None,会直接输出<Redirect>标签。客户端拿到这个SSR生成的HTML后,会立即执行重定向,即使之后hydrate时获取到了用户信息也无法挽回。
具体修复步骤
1. 同步初始化客户端状态(替换原create_effect逻辑)
把原来的create_effect改成同步执行的初始化逻辑,确保组件渲染前就设置好current_user:
let global_state = use_context::<GlobalState>().unwrap(); // 客户端环境下,同步读取localStorage设置用户状态 #[cfg(client)] { let current_user = AuthService::get_current_user(); if current_user.user_name.is_empty() { global_state.current_user.set(None); } else { global_state.current_user.set(Some(CurrentUser { user_name: create_rw_signal(current_user.user_name.clone()), })); } }
2. 适配SSR场景(通过Cookie验证用户)
如果要让SSR正确渲染受保护页面,需要把JWT存在HttpOnly Cookie里(比localStorage更安全),服务器端在渲染前验证Cookie中的JWT:
// 服务器环境下,从Cookie读取JWT并验证用户 #[cfg(server)] { let req = use_request(); let jwt_cookie = req.cookies().get("jwt").map(|c| c.value().to_string()); if let Some(jwt) = jwt_cookie { match AuthService::verify_jwt(&jwt) { Ok(user) => { global_state.current_user.set(Some(CurrentUser { user_name: create_rw_signal(user.user_name), })); } Err(_) => global_state.current_user.set(None), } } else { global_state.current_user.set(None); } }
3. 路由守卫适配异步场景(可选)
如果AuthService::get_current_user是异步操作(比如需要调用API验证JWT),要用create_resource结合Suspense等待状态加载完成,避免初始判断错误:
// 异步获取用户资源 let current_user_resource = create_resource( || (), |_| async { AuthService::get_current_user().await } ); // 根据资源更新全局状态 create_effect(move |_| { if let Some(current_user) = current_user_resource.read() { if current_user.user_name.is_empty() { global_state.current_user.set(None); } else { global_state.current_user.set(Some(CurrentUser { user_name: create_rw_signal(current_user.user_name.clone()), })); } } }); // 修改路由守卫,等待资源加载完成再判断 <Route path="/" view=move || { view! { <Show when=move || { current_user_resource.read().is_some() && global_state.current_user.get().is_none() }> <Redirect path=LOGIN_URL/> </Show> <Suspense fallback=|| view! { <div>加载中...</div> }> <Outlet/> </Suspense> } }> <Route path="/" view=HomePage/> </Route>
内容的提问来源于stack exchange,提问作者stedejan
相关产品推荐
相关产品推荐

