You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>
    );
  }
}

核心问题分析

  1. 根布局未返回完整JSX:React组件必须始终返回一个JSX元素,当前代码仅在isUserValid === true时返回内容,初始加载状态(isUserValid为false)和未登录状态下无返回值,直接导致白屏。
  2. 无限路由循环:未登录时调用router.push("/"),而根路径/会再次触发根布局的Auth检测逻辑,重复执行跳转,陷入死循环。
  3. 路由跳转目标错误:未登录时应该跳转到登录页(/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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 10:32:02