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

如何在Next.js指定页面使用Firebase认证React Hook?

问题描述

我开发了一个Next.js应用,希望通过Firebase实现用户认证。我的需求是部分页面采用客户端渲染,部分页面采用服务端渲染,但在_app.tsx中使用该认证Hook后,所有页面都变为客户端渲染。请问如何仅在指定页面使用该Hook,让仅该页面以客户端渲染方式呈现?


现有代码

_app.tsx

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <UserAuthContentProvider>
      <Layout>
        <Component {...pageProps} />
      </Layout>
    </UserAuthContentProvider>
  );
}

AuthContext Hook

export const auth = getAuth(app);
const AuthContext = createContext<any>({});
export const useAuthContextProvider = () => useContext(AuthContext);

export const UserAuthContentProvider = ({
    children,
}: {
    children: React.ReactNode;
}) => {
    const router = useRouter();
    const [user, setUser] = useState<User | null>(null);
    const [loading, setLoading] = useState(true);
    const [isUserAuthenticated, setIsUserAuthenticated] = useState(false);

    useEffect(() => {
        const unsubscribe = onAuthStateChanged(auth, (user) => {
            if (user) {
                setUser(user);
                setIsUserAuthenticated(true);
            } else {
                setUser(null);
                setIsUserAuthenticated(false);
            }
            setLoading(false);
        });
        return () => unsubscribe();
    });
};

const signUp = async (email: string, password: string) => {
    await createUserWithEmailAndPassword(auth, email, password).then((result) => {
        if (!result.user.emailVerified) {
            router.push("/verification");
        } else {
            router.push("/dashboard");
        }
    });
};

const logIn = async (email: string, password: string) => {
    await signInWithEmailAndPassword(auth, email, password).then((result) => {
        if (!result.user.emailVerified) {
            router.push("/verification");
        } else {
            router.push("/dashboard");
        }
    });
};

const logOut = async () => {
    setUser(null);
    await auth.signOut().finally(() => {
        router.push("/");
    });
};

return (
    <AuthContext.Provider
        value={{
            user,
            logIn,
            signUp,
            logOut,
            isUserAuthenticated,
        }}
    >
        {loading ? null : children}
    </AuthContext.Provider>
);

解决方案

核心原因

你的UserAuthContentProvider使用了useState、useEffect等客户端专属Hook,当在_app.tsx全局包裹时,整个App组件会被标记为客户端组件,导致所有页面强制转为客户端渲染。

具体步骤

  1. 移除全局包裹
    修改_app.tsx,去掉全局的UserAuthContentProvider,让默认页面保持服务端渲染能力:

    function MyApp({ Component, pageProps }: AppProps) {
      return (
        <Layout>
          <Component {...pageProps} />
        </Layout>
      );
    }
    
  2. 仅在指定页面使用认证Provider
    在需要客户端渲染且依赖认证的页面(如登录、注册、仪表盘)中,单独包裹UserAuthContentProvider,并添加客户端标识(Next.js 13+ App Router需显式加'use client'):

    // 示例:login.tsx(Pages Router)或 app/login/page.tsx(App Router)
    'use client'; // App Router必须加,Pages Router可选但建议添加
    
    import { UserAuthContentProvider } from '../path/to/auth-context';
    import { useAuthContextProvider } from '../path/to/auth-context';
    
    export default function LoginPage() {
      const { logIn } = useAuthContextProvider();
    
      const handleLogin = async () => {
        await logIn('user@example.com', 'password123');
      };
    
      return (
        <UserAuthContentProvider>
          <div>
            <h1>登录</h1>
            <button onClick={handleLogin}>登录</button>
          </div>
        </UserAuthContentProvider>
      );
    }
    
  3. 修复AuthContext代码问题
    原AuthContext存在语法错误(return部分在函数外部),同时补充依赖数组、错误处理,确保组件正确运行:

    'use client'; // 标记为客户端组件
    
    import { getAuth, onAuthStateChanged, createUserWithEmailAndPassword, signInWithEmailAndPassword } from 'firebase/auth';
    import { app } from '../path/to/firebase-config';
    import { createContext, useContext, useState, useEffect } from 'react';
    import { useRouter } from 'next/router'; // Pages Router用此,App Router用next/navigation的useRouter
    
    export const auth = getAuth(app);
    const AuthContext = createContext<any>({});
    export const useAuthContextProvider = () => useContext(AuthContext);
    
    export const UserAuthContentProvider = ({
      children,
    }: {
      children: React.ReactNode;
    }) => {
      const router = useRouter();
      const [user, setUser] = useState<User | null>(null);
      const [loading, setLoading] = useState(true);
      const [isUserAuthenticated, setIsUserAuthenticated] = useState(false);
    
      useEffect(() => {
        const unsubscribe = onAuthStateChanged(auth, (user) => {
          if (user) {
            setUser(user);
            setIsUserAuthenticated(true);
          } else {
            setUser(null);
            setIsUserAuthenticated(false);
          }
          setLoading(false);
        });
        return () => unsubscribe();
      }, []); // 添加空依赖数组,避免重复订阅
    
      const signUp = async (email: string, password: string) => {
        try {
          const result = await createUserWithEmailAndPassword(auth, email, password);
          if (!result.user.emailVerified) {
            router.push("/verification");
          } else {
            router.push("/dashboard");
          }
        } catch (error) {
          console.error('注册失败:', error);
        }
      };
    
      const logIn = async (email: string, password: string) => {
        try {
          const result = await signInWithEmailAndPassword(auth, email, password);
          if (!result.user.emailVerified) {
            router.push("/verification");
          } else {
            router.push("/dashboard");
          }
        } catch (error) {
          console.error('登录失败:', error);
        }
      };
    
      const logOut = async () => {
        try {
          setUser(null);
          await auth.signOut();
          router.push("/");
        } catch (error) {
          console.error('登出失败:', error);
        }
      };
    
      return (
        <AuthContext.Provider
          value={{
            user,
            logIn,
            signUp,
            logOut,
            isUserAuthenticated,
          }}
        >
          {loading ? null : children}
        </AuthContext.Provider>
      );
    };
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 08:11:40