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)
问题根源分析
- 渲染阶段直接调用路由跳转:在组件渲染逻辑中直接执行
router.push('/signin'),会触发React状态更新导致重复渲染,同时Next.js 13 App Router的客户端组件初始化阶段,全局location对象可能尚未就绪,引发未定义错误。 - 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
相关产品推荐
相关产品推荐

