使用Astro+React+RRDv6开发SPA刷新页面空白并触发useNavigate错误
我用Astro、React和React Router Dom v6构建了一个混合SPA,路由结构如下:
localhost/—— 公开页面localhost/login—— 管理员登录页面localhost/admin/home—— 管理员仪表盘页面
登录后跳转仪表盘无异常,但刷新页面模拟获取最新数据时,控制台抛出错误:
Uncaught Error:
useNavigate()may be used only in the context of a<Router>component.
页面同时变为空白,是不是遗漏了RRDv6的关键概念或配置?
环境与依赖信息
env: node 16.13.0, npm 8.3.0 deps: @astrojs/react 2.0.2, astro 2.0.16, react 18.2.0, react-router-dom 6.11.1 script: dev = astro dev cmd: npm run dev
现有路由代码
export default function Admin() { const [cookie, setCookie, removeCookie] = useCookies(['token']); const PrivateRoute = () => { const location = useLocation() const isAuthenticated = () => { return cookie.token !== null && cookie.token !== undefined; }; return isAuthenticated() ? <Outlet /> : <Navigate to="/admin" replace state={{ from: location }} />; }; return ( <BrowserRouter> <Routes> <Route path="/login" element={<AdminLogin />} /> <Route path="/admin" element={<PrivateRoute />}> <Route path="home" element={<AdminHome />} /> </Route> </Routes> </BrowserRouter> ); }
管理员仪表盘代码
export default function AdminHome() { const [cookie, setCookie, removeCookie] = useCookies(['token']); const [doLogout, setDoLogout] = useState(false) const navigate= useNavigate() useEffect(() => { if (doLogout) { removeCookie('token', { sameSite: 'none', secure: true }) navigate('/login') } }, [doLogout]) return ( <div> <AdminAppbar cookie={cookie} logout={setDoLogout} /> <AdminBlog/> </div> ); }
问题原因与解决方案
核心原因
- Router上下文范围错误:你在
Admin组件内部定义了PrivateRoute,而PrivateRoute里的useLocation在BrowserRouter的上下文之外执行(组件定义阶段就调用了钩子),刷新时初始渲染无法获取Router上下文。 - Astro渲染机制冲突:Astro默认会尝试服务端渲染组件,但
BrowserRouter是纯客户端组件,服务端渲染时useNavigate等RRD钩子无法找到Router上下文,直接报错。
修复步骤
1. 抽离PrivateRoute为独立组件
将需要使用RRD钩子的组件从Router组件中分离,确保所有钩子调用都在BrowserRouter上下文内:
// src/components/PrivateRoute.jsx import { useLocation, Navigate, Outlet } from 'react-router-dom'; import { useCookies } from 'react-cookie'; export default function PrivateRoute() { const [cookie] = useCookies(['token']); const location = useLocation(); const isAuthenticated = () => { return cookie.token !== null && cookie.token !== undefined; }; return isAuthenticated() ? <Outlet /> : <Navigate to="/login" replace state={{ from: location }} />; }
2. 重构Router组件
将Router逻辑单独封装,确保BrowserRouter包裹所有路由相关组件:
// src/components/AdminRouter.jsx import { BrowserRouter, Routes, Route } from 'react-router-dom'; import PrivateRoute from './PrivateRoute'; import AdminLogin from './AdminLogin'; import AdminHome from './AdminHome'; export default function AdminRouter() { return ( <BrowserRouter> <Routes> <Route path="/login" element={<AdminLogin />} /> <Route path="/admin" element={<PrivateRoute />}> <Route path="home" element={<AdminHome />} /> </Route> </Routes> </BrowserRouter> ); }
3. 标记为客户端组件
在Astro页面中引入Router组件时,添加client:only="react"指令,强制Astro跳过服务端渲染,只在客户端执行:
// src/pages/admin/home.astro --- import AdminRouter from '../components/AdminRouter'; --- <AdminRouter client:only="react" />
关键注意事项
- 所有使用
useNavigate、useLocation等RRD钩子的组件,必须被包裹在BrowserRouter内部。 - Astro中涉及客户端路由的组件,必须明确标记为客户端组件,避免服务端渲染时的上下文缺失问题。
- 不要在Router组件内部嵌套定义需要使用RRD钩子的子组件,确保钩子调用始终在Router上下文范围内。
内容的提问来源于stack exchange,提问作者mitchiri_neko
相关产品推荐
相关产品推荐

