返回上一页时useState仍存旧值,AuthGuard闪现无权限页面求助
权限守卫回退异常问题排查与解决
问题描述
所有页面通过AuthGuard组件控制子组件显示或跳转,首次访问功能正常,但浏览器回退时出现异常:
- 未登录状态下访问登录用户专属的
/home,会被正确重定向到/login; - 点击浏览器左上角返回按钮(非站内链接)回到上一页;
- 会短暂显示
/home内容,随后被AuthGuard重定向回/login。
排查发现,控制子组件显示的布尔类型useState状态在回退时会短暂保留旧值(/login页面加载时设置的true),无法重置为初始的false。
AuthGuard组件代码
const AuthGuard = ({ children, restriction }) => { const [canShowChildren, setCanShowChildren] = useState<boolean>(false); const { user, loading } = useAuth(); const router = useRouter(); // 因部分用户数据需异步获取,故必须用useEffect+async,这部分逻辑与当前问题无关未贴全 useEffect(() => { (async () => { if (loading || !router.isReady) return; if (restriction === "public") { setCanShowChildren(true); return; } if (!user) { router.push("/login"); return; } ... })(); }, [loading, restriction, router, user]); if (loading || !router.isReady) { return <Loading />; } return canShowChildren ? children : <Loading />; }
页面设置restriction的示例代码
export async function getStaticProps() { return { props: { restriction: "public", }, }; }
问题原因
这是Next.js的浏览器缓存与页面复用机制导致的:当从/login回退到/home时,浏览器不会重新初始化AuthGuard组件,而是复用之前已挂载的组件实例,因此useState的初始值false不会被重置,反而保留了/login页面时设置的true。
同时,useEffect是异步执行的——在它完成权限校验逻辑之前,组件已经基于旧的canShowChildren=true渲染了/home的内容,直到useEffect触发重定向,才会跳回/login,这就造成了短暂显示的问题。
解决方案
方案1:监听路由变化,强制重置状态
在AuthGuard中监听router.asPath的变化,每次路由切换时主动重置canShowChildren为初始值:
const AuthGuard = ({ children, restriction }) => { const [canShowChildren, setCanShowChildren] = useState<boolean>(false); const { user, loading } = useAuth(); const router = useRouter(); // 路由切换时重置状态 useEffect(() => { setCanShowChildren(false); }, [router.asPath]); useEffect(() => { (async () => { if (loading || !router.isReady) return; if (restriction === "public") { setCanShowChildren(true); return; } if (!user) { router.push("/login"); return; } // 其他权限校验逻辑 setCanShowChildren(true); })(); }, [loading, restriction, router, user]); if (loading || !router.isReady) { return <Loading />; } return canShowChildren ? children : <Loading />; }
方案2:移除状态管理,直接依赖实时判断结果
不需要用canShowChildren状态存值,直接根据当前权限逻辑决定是否渲染内容,彻底避免状态残留问题:
const AuthGuard = ({ children, restriction }) => { const { user, loading } = useAuth(); const router = useRouter(); useEffect(() => { (async () => { if (loading || !router.isReady) return; // 非公开页面且未登录时重定向 if (restriction !== "public" && !user) { router.push("/login"); } })(); }, [loading, restriction, router, user]); if (loading || !router.isReady) { return <Loading />; } // 直接判断是否允许显示:公开页面 或 已登录用户 const canShow = restriction === "public" || !!user; return canShow ? children : <Loading />; }
方案3:利用Next.js路由事件提前处理
通过Next.js路由的beforePopState事件,在浏览器回退动作触发前就重置状态:
const AuthGuard = ({ children, restriction }) => { const [canShowChildren, setCanShowChildren] = useState<boolean>(false); const { user, loading } = useAuth(); const router = useRouter(); useEffect(() => { const handleBeforePopState = () => { // 回退前重置状态 setCanShowChildren(false); return true; // 允许回退动作继续 }; router.beforePopState(handleBeforePopState); // 组件卸载时清理事件 return () => { router.beforePopState(() => true); }; }, [router]); useEffect(() => { (async () => { if (loading || !router.isReady) return; if (restriction === "public") { setCanShowChildren(true); return; } if (!user) { router.push("/login"); return; } setCanShowChildren(true); })(); }, [loading, restriction, router, user]); if (loading || !router.isReady) { return <Loading />; } return canShowChildren ? children : <Loading />; }
内容的提问来源于stack exchange,提问作者bler
相关产品推荐
相关产品推荐

