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

使用Next JS 14+Firebase登录后出现反复重定向问题求助

登录后页面反复重定向的原因及解决方案

问题背景

用Next JS 14、shadcn/ui和Firebase开发应用,实现了通过signInWithPopup完成第三方登录后跳转至/home路由的功能。同时做了两个路由守卫:

  • 根页面(src/app/page.tsx):若用户已登录,重定向到/home
  • 首页(src/app/home/page.tsx):若用户未登录,重定向回根目录

但登录后出现页面反复在根目录和/home之间跳转的问题。

用户代码如下:

src/app/page.tsx

"use client";

import { useEffect } from "react";

import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";

import {
  FaGoogle,
  FaGithub,
  FaFacebook
} from "react-icons/fa";

import { app } from "@/lib/firebase";
import {
  getAuth,
  signInWithPopup,
  GoogleAuthProvider,
  GithubAuthProvider,
  FacebookAuthProvider
} from "firebase/auth";
import { useAuthState } from "react-firebase-hooks/auth";

import { useRouter } from "next/navigation";

export default function Home() {
  const auth = getAuth(app);
  const router = useRouter();
  const [user, loading] = useAuthState(auth);

  useEffect(() => {
    if (user) {
      window.location.href = "/home";
    }
  }, [user]);

  const withGoogle = () => {
    const provider = new GoogleAuthProvider();

    signInWithPopup(auth, provider)
      .then(() => {
        router.push("/home");
      })
      .catch((err) => {
        console.error("An error occured: " + err);
      });
  }

  const withGithub = () => {
    const provider = new GithubAuthProvider();

    signInWithPopup(auth, provider)
      .then(() => {
        router.push("/home");
      })
      .catch((err) => {
        console.error("An error occured: " + err);
      });
  }

  const withFacebook = () => {
    const provider = new FacebookAuthProvider();

    signInWithPopup(auth, provider)
      .then(() => {
        router.push("/home");
      })
      .catch((err) => {
        console.error("An error occured: " + err);
      });
  }

  return (
    <main>
      {loading || user ? (
        <p> Loading... </p>
      ) : (
        <div className="flex justify-center pt-64">
          <Card className="w-96">
            <CardHeader>
              <CardTitle> Sign In </CardTitle>
              <CardDescription>
                Sign in using one of these methods
              </CardDescription>
            </CardHeader>
            <CardContent>
              <div>
                <Button onClick={withGoogle}>
                  <FaGoogle className="mr-2 h-4 w-4" /> Sign In with Google
                </Button>
              </div>
              <div className="pt-3">
                <Button onClick={withGithub}>
                  <FaGithub className="mr-2 h-4 w-4" /> Sign In with Github
                </Button>
              </div>
              <div className="pt-3">
                <Button onClick={withFacebook}>
                  <FaFacebook className="mr-2 h-4 w-4" /> Sign In with Facebook
                </Button>
              </div>
            </CardContent>
          </Card>
        </div>
      )}
    </main>
  )
}

src/app/home/page.tsx

"use client";

import { app } from "@/lib/firebase";
import { getAuth } from "firebase/auth";
import { useAuthState } from "react-firebase-hooks/auth";

import { useEffect } from "react";

function Home() {
  const auth = getAuth(app);
  const [user, loading] = useAuthState(auth);

  useEffect(() => {
    if (!user) {
      window.location.href = "/";
    }
  }, [user]);

  return (
    <div>
      {loading || !user ? (
        <p> Loading... </p>
      ) : (
        <p> Hi Mom </p>
      )}
    </div>
  )
}

export default Home;

核心原因

  1. 未等待Auth状态初始化完成
    useAuthState在组件挂载初期会处于loading状态,此时user为undefined。登录后跳转到/home时,Firebase还没完成用户状态同步,/home页面会因!user触发重定向回根目录;而根页面此时已同步到用户状态,又重定向回/home,形成循环。

  2. 混合使用两种导航方式
    window.location.href会触发页面全量刷新,刷新后Firebase Auth需要重新初始化状态,再次进入loading阶段,加剧了状态波动;而router.push是Next.js客户端导航,不会刷新页面,状态保持更稳定。

  3. 重定向逻辑未排除加载状态
    两个页面的重定向逻辑都没有判断loading是否结束,导致在Auth状态未稳定时就触发跳转。

解决方案

1. 重定向前先判断加载状态

确保只有当Auth状态完全初始化(loading为false)时,才根据user状态执行重定向。

2. 统一使用Next.js客户端导航

替换所有window.location.href为router.push,避免页面刷新导致的状态重置。

修改后的代码

src/app/page.tsx

"use client";

import { useEffect } from "react";

import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";

import { FaGoogle, FaGithub, FaFacebook } from "react-icons/fa";

import { app } from "@/lib/firebase";
import {
  getAuth,
  signInWithPopup,
  GoogleAuthProvider,
  GithubAuthProvider,
  FacebookAuthProvider
} from "firebase/auth";
import { useAuthState } from "react-firebase-hooks/auth";

import { useRouter } from "next/navigation";

export default function LoginPage() {
  const auth = getAuth(app);
  const router = useRouter();
  const [user, loading] = useAuthState(auth);

  useEffect(() => {
    // 等待加载完成后再判断用户状态
    if (!loading && user) {
      router.push("/home");
    }
  }, [user, loading, router]);

  const withGoogle = () => {
    const provider = new GoogleAuthProvider();
    signInWithPopup(auth, provider)
      .then(() => router.push("/home"))
      .catch((err) => console.error("登录错误:", err));
  };

  const withGithub = () => {
    const provider = new GithubAuthProvider();
    signInWithPopup(auth, provider)
      .then(() => router.push("/home"))
      .catch((err) => console.error("登录错误:", err));
  };

  const withFacebook = () => {
    const provider = new FacebookAuthProvider();
    signInWithPopup(auth, provider)
      .then(() => router.push("/home"))
      .catch((err) => console.error("登录错误:", err));
  };

  if (loading) {
    return <p>Loading...</p>;
  }

  if (user) {
    return null; // 重定向过程中避免内容闪烁
  }

  return (
    <main>
      <div className="flex justify-center pt-64">
        <Card className="w-96">
          <CardHeader>
            <CardTitle>Sign In</CardTitle>
            <CardDescription>Sign in using one of these methods</CardDescription>
          </CardHeader>
          <CardContent>
            <div>
              <Button onClick={withGoogle}>
                <FaGoogle className="mr-2 h-4 w-4" /> Sign In with Google
              </Button>
            </div>
            <div className="pt-3">
              <Button onClick={withGithub}>
                <FaGithub className="mr-2 h-4 w-4" /> Sign In with Github
              </Button>
            </div>
            <div className="pt-3">
              <Button onClick={withFacebook}>
                <FaFacebook className="mr-2 h-4 w-4" /> Sign In with Facebook
              </Button>
            </div>
          </CardContent>
        </Card>
      </div>
    </main>
  );
}

src/app/home/page.tsx

"use client";

import { app } from "@/lib/firebase";
import { getAuth } from "firebase/auth";
import { useAuthState } from "react-firebase-hooks/auth";
import { useEffect } from "react";
import { useRouter } from "next/navigation";

function Home() {
  const auth = getAuth(app);
  const router = useRouter();
  const [user, loading] = useAuthState(auth);

  useEffect(() => {
    // 等待加载完成后再判断用户状态
    if (!loading && !user) {
      router.push("/");
    }
  }, [user, loading, router]);

  if (loading || !user) {
    return <p>Loading...</p>;
  }

  return <p>Hi Mom</p>;
}

export default Home;

额外优化建议

  • 把Auth状态判断逻辑封装成自定义Hook(比如useAuthRedirect),减少重复代码。
  • 考虑使用Next.js的Middleware做路由级校验,在页面渲染前就完成Auth状态判断,彻底避免客户端重定向的闪烁和循环问题。

内容的提问来源于stack exchange,提问作者Nathan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 01:19:53