Next.js报错:invariant expected app router to be mounted 授权拦截异常
未授权访问拦截逻辑问题排查与修复
问题描述
我在实现「未授权用户访问主页面时自动拦截并跳转到登录页」的逻辑,但目前无论用户是否已登录,访问根路径/时都会出现白屏,同时抛出错误。
当前文件夹结构
├── src │ ├── app │ │ ├── (auth) │ │ │ ├── layout.tsx │ │ │ ├── setup-profile │ │ │ │ └── page.tsx │ │ │ ├── sign-in │ │ │ │ └── page.tsx │ │ │ └── sign-up │ │ │ └── page.tsx │ │ └── (main) │ │ ├── layout.tsx │ │ └── page.tsx
现有根布局代码
"use client"; export default function RootLayout({ children, }: { children: React.ReactNode; }) { const router = useRouter(); const [isUserValid, setIsUserValid] = useState(false); useEffect(() => { onAuthStateChanged(auth, (user) => { if (user) { console.log(user); setIsUserValid(true); } else { router.push("/"); } }); }, []); if (isUserValid === true) { return ( <html lang="en"> <body className={roboto.variable}> {children} <LogOutModal /> <UserProfile /> </body> </html> ); } }
核心问题分析
- 根布局未返回完整JSX:React组件必须始终返回一个JSX元素,当前代码仅在
isUserValid === true时返回内容,初始加载状态(isUserValid为false)和未登录状态下无返回值,直接导致白屏。 - 无限路由循环:未登录时调用
router.push("/"),而根路径/会再次触发根布局的Auth检测逻辑,重复执行跳转,陷入死循环。 - 路由跳转目标错误:未登录时应该跳转到登录页(
/sign-in),而非根路径/。
修复方案
1. 完善根布局的返回逻辑
添加加载状态占位,并确保所有分支都返回JSX:
"use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { onAuthStateChanged } from "firebase/auth"; import { auth } from "@/firebase/config"; // 确保引入你的Firebase auth实例 export default function RootLayout({ children, }: { children: React.ReactNode; }) { const router = useRouter(); const [isUserValid, setIsUserValid] = useState<boolean | null>(null); // 用null表示加载中 useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (user) => { if (user) { setIsUserValid(true); } else { // 未登录时跳转到登录页,而非根路径 router.push("/sign-in"); setIsUserValid(false); } }); // 清理订阅,避免内存泄漏 return () => unsubscribe(); }, [router]); // 加载中显示占位(比如骨架屏、加载动画) if (isUserValid === null) { return ( <html lang="en"> <body> <div className="flex items-center justify-center h-screen"> <p>加载中...</p> </div> </body> </html> ); } // 已登录时渲染正常内容 return ( <html lang="en"> <body className={roboto.variable}> {children} <LogOutModal /> <UserProfile /> </body> </html> ); }
2. 优化权限控制层级
根布局是全局的,更推荐在(main)组布局中做权限拦截,避免影响(auth)下的登录相关页面:
// src/app/(main)/layout.tsx "use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import { onAuthStateChanged } from "firebase/auth"; import { auth } from "@/firebase/config"; export default function MainLayout({ children, }: { children: React.ReactNode; }) { const router = useRouter(); const [isLoading, setIsLoading] = useState(true); useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (user) => { if (!user) { router.push("/sign-in"); } setIsLoading(false); }); return () => unsubscribe(); }, [router]); if (isLoading) { return <div className="flex items-center justify-center h-screen">加载中...</div>; } return <>{children}</>; }
3. 调整根布局职责
将根布局恢复为全局基础布局,只处理通用样式、全局组件,把权限控制交给组布局或页面:
// src/app/layout.tsx // 不需要"use client",保持服务端组件(如果不需要客户端逻辑) import { Roboto } from "next/font/google"; const roboto = Roboto({ variable: "--font-roboto", subsets: ["latin"], }); export default function RootLayout({ children, }: { children: React.ReactNode; }) { return ( <html lang="en"> <body className={roboto.variable}> {children} </body> </html> ); }
随后可把登录态相关的全局组件(LogOutModal、UserProfile)移到(main)布局中,避免在未登录时渲染。
内容的提问来源于stack exchange,提问作者user21209760
相关产品推荐
相关产品推荐

