Next.js+Redux+Firebase Auth:页面渲染前验证用户登录权限方案
解决方案:Next.js + Redux + Firebase Auth 私有页面权限控制
你遇到的核心问题是Firebase Auth的状态初始化是异步操作,当你的AdminLayout组件首次componentDidMount时,Redux store里的currentUser还没从Firebase同步完成,所以拿不到值。下面给你一套完整的解决思路和代码修改方案:
1. 确保Redux与Firebase Auth状态实时同步
首先要保证你的Redux能实时监听Firebase Auth的状态变化,而不是只在页面加载时获取一次。在你的用户action文件里添加一个监听Auth状态的方法:
// redux/user/user.actions.js import firebase from 'firebase/app'; import 'firebase/auth'; import 'firebase/firestore'; export const listenToAuthState = () => (dispatch) => { // 监听Firebase Auth状态变化 firebase.auth().onAuthStateChanged(async (authUser) => { if (authUser) { // 如果用户已登录,从Firestore获取用户的role信息(假设你把角色存在users集合里) const userDoc = await firebase.firestore().collection('users').doc(authUser.uid).get(); const userData = { ...authUser, role: userDoc.data()?.role || 'user' // 兜底默认角色 }; dispatch({ type: 'SET_CURRENT_USER', payload: userData }); } else { dispatch({ type: 'SET_CURRENT_USER', payload: null }); } }); };
然后在你的_app.jsx(Next.js的全局入口)里启动这个监听,确保页面加载时就开始同步Auth状态:
// pages/_app.jsx import { useEffect } from 'react'; import { useDispatch } from 'react-redux'; import { listenToAuthState } from '../redux/user/user.actions'; function MyApp({ Component, pageProps }) { const dispatch = useDispatch(); useEffect(() => { // 页面加载时立即监听Auth状态 const unsubscribe = dispatch(listenToAuthState()); // 组件卸载时取消监听 return () => unsubscribe(); }, [dispatch]); return <Component {...pageProps} />; } export default MyApp;
2. 修改AdminLayout,监听currentUser的变化
你的AdminLayout目前只在componentDidMount里检查一次,但此时Auth状态可能还没同步完成。需要添加componentDidUpdate来监听currentUser的变化,确保状态更新后立即校验权限:
// admin-layout.component.jsx // Imports class AdminLayout extends React.Component { componentDidMount() { this.checkUserPermissions(); } componentDidUpdate(prevProps) { // 当currentUser状态发生变化时,重新校验权限 if (this.props.currentUser !== prevProps.currentUser) { this.checkUserPermissions(); } } checkUserPermissions = () => { const { currentUser } = this.props; // 未登录用户直接跳转登录页 if (!currentUser) { Router.push("/admin/login"); return; } // 已登录但不是admin角色,同样跳转登录(或无权限页面) if (currentUser.role !== "admin") { Router.push("/admin/login"); // 可选:跳转到自定义的无权限页面 // Router.push("/admin/access-denied"); } }; render() { // 可以添加加载状态,避免Auth状态未同步时显示私有内容 if (!this.props.currentUser) { return <div>加载中...</div>; } // 非admin角色也可以在这里直接不渲染内容 if (this.props.currentUser.role !== "admin") { return null; // 或者显示加载中直到跳转完成 } return ( <React.Fragment> // 你的导航栏等公共组件 {this.props.children} // 其他公共内容 </React.Fragment> ); } } const mapStateToProps = (state) => ({ currentUser: state.user.currentUser, }); export default connect(mapStateToProps)(AdminLayout);
3. 可选:路由层面的额外保护(Next.js 12及以下)
如果你想在路由层面直接拦截未授权访问,可以在_app.jsx里全局监听路由变化,校验权限:
// pages/_app.jsx import { useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useRouter } from 'next/router'; import { listenToAuthState } from '../redux/user/user.actions'; function MyApp({ Component, pageProps }) { const dispatch = useDispatch(); const router = useRouter(); const currentUser = useSelector(state => state.user.currentUser); useEffect(() => { const unsubscribe = dispatch(listenToAuthState()); return () => unsubscribe(); }, [dispatch]); useEffect(() => { // 定义需要admin权限的路由 const protectedRoutes = ['/admin', '/admin/dashboard', '/admin/settings']; const isProtectedRoute = protectedRoutes.includes(router.pathname); if (isProtectedRoute) { if (!currentUser) { router.push('/admin/login'); } else if (currentUser.role !== 'admin') { router.push('/admin/login'); } } }, [currentUser, router.pathname]); return <Component {...pageProps} />; } export default MyApp;
关键要点总结
- Firebase Auth的状态是异步加载的,必须通过
onAuthStateChanged监听,不能依赖首次挂载时的同步获取 - 组件内要监听
currentUser的变化,而不是只在挂载时检查一次 - 可以添加加载状态,避免Auth未同步时泄露私有页面的布局结构
内容的提问来源于stack exchange,提问作者user10261767
相关产品推荐
相关产品推荐

