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

使用Firebase与Redux在Next.js的_app.js中做访问控制时的页面闪现问题

问题原因及解决方案

问题描述

我开发了一个应用,未登录用户可访问网站,但仅已认证用户能访问/app、/app/*路径。现有代码功能正常,但未登录访问/app时,会先短暂显示页面内容,再提示“您无权访问该页面”。请问这是什么原因?

用户提供的原始代码:

import { Provider, useDispatch, useSelector } from 'react-redux';
import { useRouter } from 'next/router';
import '../styles/front.css';
import '../styles/app.css';
import React, { useEffect, useState } from 'react';
import { wrapper, store } from '../store';
import { login, logout, selectUser } from "../redux/slices/userSlice";
import { auth } from '../firebase';

function MyApp({ Component, pageProps }) {
  const user = useSelector(selectUser);
  const dispatch = useDispatch();
  const router = useRouter();
  const isAppPage = router.pathname.startsWith('/app');
  const [shouldRender, setShouldRender] = useState(true); // New state variable
  const [loading, setLoading] = useState(true);
  const [isAuthenticated, setIsAuthenticated] = useState(false);

  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged((userAuth) => {
      if (userAuth) {
        dispatch(login({
          email: userAuth.email,
          uid: userAuth.uid,
          displayName: userAuth.displayName,
          photoUrl: userAuth.photoURL
        }));
        setIsAuthenticated(true);
      } else {
        if (isAppPage) {
          setShouldRender(false);
        }
        dispatch(logout());
        setIsAuthenticated(false);
      }
      setLoading(false); // Set loading to false once the authentication status is checked
    });

    return () => unsubscribe(); // Cleanup the event listener when the component unmounts
  }, []);

  return (
    <Provider store={store}>
      {shouldRender ? <Component {...pageProps} /> : <p>You're not allowed to access that page.</p>}
    </Provider>
  );
}

export default wrapper.withRedux(MyApp);

核心原因

  1. 初始状态默认允许渲染:shouldRender初始值设为true,而Firebase的auth.onAuthStateChanged是异步执行的——组件挂载后会先渲染目标页面内容,等Firebase返回认证状态结果后,才会修改shouldRender为false,这就导致了短暂的未授权内容泄露。
  2. 依赖项缺失:useEffect的依赖数组为空,但内部用到了isAppPage(依赖router.pathname),当路由切换时,逻辑不会重新计算,可能导致判断失效。
  3. 状态冗余:已经通过Redux的selectUser获取用户状态,单独维护isAuthenticated状态容易出现状态不一致问题。

修复后的代码

import { Provider, useDispatch, useSelector } from 'react-redux';
import { useRouter } from 'next/router';
import '../styles/front.css';
import '../styles/app.css';
import React, { useEffect, useState } from 'react';
import { wrapper, store } from '../store';
import { login, logout, selectUser } from "../redux/slices/userSlice";
import { auth } from '../firebase';

function MyApp({ Component, pageProps }) {
  const user = useSelector(selectUser);
  const dispatch = useDispatch();
  const router = useRouter();
  const isAppPage = router.pathname.startsWith('/app');
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const unsubscribe = auth.onAuthStateChanged((userAuth) => {
      if (userAuth) {
        dispatch(login({
          email: userAuth.email,
          uid: userAuth.uid,
          displayName: userAuth.displayName,
          photoUrl: userAuth.photoURL
        }));
      } else {
        dispatch(logout());
        // 未登录访问app页面时,可选择跳转首页或显示提示
        if (isAppPage) {
          router.push('/');
        }
      }
      setLoading(false);
    });

    return () => unsubscribe();
  }, [dispatch, isAppPage, router]);

  // 加载状态下显示加载内容,避免提前渲染未授权页面
  if (loading) {
    return <div>加载中...</div>; // 可替换为自定义加载组件
  }

  // 未登录访问app页面时,显示权限提示
  if (isAppPage && !user) {
    return <p>您无权访问该页面。</p>;
  }

  return (
    <Provider store={store}>
      <Component {...pageProps} />
    </Provider>
  );
}

export default wrapper.withRedux(MyApp);

关键修改说明

  • 利用加载状态阻塞渲染:在认证状态未确定前,只显示加载内容,彻底避免未授权内容提前显示。
  • 修复依赖项:将dispatch、isAppPage、router加入useEffect依赖数组,确保路由切换时逻辑能重新执行。
  • 简化状态判断:直接使用Redux中的user状态判断认证状态,去掉冗余状态,减少不一致风险。
  • 可选体验优化:未登录访问/app时,可选择直接跳转到首页,替代静态提示文本(可根据业务需求调整)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 18:13:25