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

NextJS 13+React中Location未定义错误:保护路由跳转问题

解决Next.js 13+中保护路由跳转时的"location is not defined"错误

问题背景

在Next.js 13+ React项目中基于Firebase实现保护路由,未登录用户访问Profile页时,本应跳转至signin页面,但出现以下错误:

error node_modules/next/dist/client/components/app-router.js (144:65) @ location

error ReferenceError: location is not defined
    at Profile (./src/app/profile/page.js:32:20)

问题根源分析

  1. 渲染阶段直接调用路由跳转:在组件渲染逻辑中直接执行router.push('/signin'),会触发React状态更新导致重复渲染,同时Next.js 13 App Router的客户端组件初始化阶段,全局location对象可能尚未就绪,引发未定义错误。
  2. Auth状态初始化逻辑模糊:AuthContext中user初始值设为null,无法区分"Auth未完成初始化"和"已确认无用户"两种状态,导致在Firebase用户状态同步完成前就触发跳转。

解决方案及代码修改

1. 修改Profile组件:避免渲染阶段执行导航,添加加载状态

'use client'

import Navbar from '@/components/UI/Navbar'
import React, { useState, useEffect } from 'react'
import { UserAuth } from '../context/AuthContext'
import { useRouter } from 'next/navigation';
import Modal from '@/components/UI/Modal';

const Profile = () => {
    const [hasData, setHasData] = useState(false)
    const [showModal, setShowModal] = useState(false)
    const [data, setData] = useState()

    const { user } = UserAuth()
    const router = useRouter()

    // 用useEffect包裹导航逻辑,确保在组件挂载后执行
    useEffect(() => {
        if (user === null) {
            router.push('/signin')
        }
    }, [user, router])

    // Auth初始化中,显示加载状态
    if (user === undefined) {
        return <div className="flex items-center justify-center h-screen">加载中...</div>
    }

    // 已确认无用户,返回null(此时useEffect会触发跳转)
    if (!user) {
        return null
    }

    return (
        <div>
            <Navbar />
            <main>
                <div className="mx-auto max-w-7xl py-6 sm:px-6 lg:px-8">
                    <h1 className='text-2xl font-bold'>Welcome Back</h1>
                    <p className='text-sm font-normal'>View your submitted forms here</p>

                    {!hasData ? (
                        <p className='text-center mt-10'>You have not submitted any forms.</p>
                    ) : (
                        <div className="overflow-x-auto px-5 mx-auto mt-10 w-5/6 ">
                            <table className="table">
                                <thead>
                                    <tr className='grid grid-cols-4'>
                                        <th>Name</th>
                                        <th>Status</th>
                                        <th></th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <tr className='grid grid-cols-4'>
                                        <td className='flex items-center'>
                                            <div>
                                                <div className="font-bold">Form 1</div>
                                            </div>
                                        </td>
                                        <td className='col-span-2'>
                                            <div className='rounded-md w-fit bg-yellow-400 px-3 py-1 text-sm font-semibold text-white shadow-sm'>{data.status}</div>
                                        </td>
                                        <th>
                                            <button
                                                className="rounded-md w-fit bg-indigo-600 px-3 py-1 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
                                                onClick={() => setShowModal(true)}
                                            >
                                                Details
                                            </button>
                                        </th>
                                    </tr>
                                </tbody>
                            </table>
                        </div>
                    )}
                </div>
            </main>
            <Modal isVisible={showModal} onClose={() => setShowModal(false)} data={data} />
        </div>
    )
}

export default Profile

2. 修改AuthContext:优化初始状态与用户属性处理

import { useContext, createContext, useState, useEffect } from "react";
import {
    signInWithPopup,
    signOut,
    onAuthStateChanged,
    GoogleAuthProvider,
    signInWithEmailAndPassword
} from "firebase/auth";

import { auth } from "@/config/firebase";

const AuthContext = createContext();

export const AuthContextProvider = ({ children }) => {
    // 初始值设为undefined,区分未初始化和已确认无用户
    const [user, setUser] = useState(undefined);

    const googleSignIn = () => {
        const provider = new GoogleAuthProvider();
        signInWithPopup(auth, provider);
    };

    const logOut = () => {
        return signOut(auth);
    };

    const login = (email, password) => {
        return signInWithEmailAndPassword(auth, email, password)
    }

    useEffect(() => {
        const unsubscribe = onAuthStateChanged(auth, async (currentUser) => {
            if (currentUser) {
                const idTokenResult = await currentUser.getIdTokenResult();
                // 不直接修改Firebase用户对象,创建新对象添加admin属性
                const userWithAdmin = {
                    ...currentUser,
                    admin: idTokenResult.claims.admin
                };
                localStorage.setItem('authUser', JSON.stringify(userWithAdmin))
                setUser(userWithAdmin);
            } else {
                localStorage.removeItem('authUser')
                setUser(null);
            }
        });
        return () => unsubscribe();
    }, []);

    return (
        <AuthContext.Provider value={{ user, googleSignIn, logOut, login }}>
            {children}
        </AuthContext.Provider>
    );
};

export const UserAuth = () => {
    return useContext(AuthContext);
};

修改说明

  • 导航逻辑移至useEffect:确保路由跳转在组件挂载完成后执行,此时Next.js的路由环境已完全就绪,避免location未定义错误。
  • 区分Auth状态:用undefined表示初始化中,null表示已确认无用户,避免提前触发跳转。
  • 避免修改Firebase原生对象:通过扩展对象添加admin属性,防止破坏Firebase用户实例的原有结构。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 16:47:35