使用useSession钩子在客户端获取session时为何返回undefined?
登录页Session获取异常与页面保护优化
问题描述
需要实现页面保护逻辑:已登录用户访问/login页面时自动跳转到首页,但在login page.tsx中使用useSession钩子获取的session始终为undefined,而首页调用的UserSession组件却能正常获取session。
问题代码
login page.tsx
"use client"; import React, { FormEvent, useEffect } from "react"; import { signIn, useSession } from "next-auth/react"; import { useRouter } from "next/navigation"; const page = () => { const {data: session, status} = useSession(); const router = useRouter(); useEffect(() => { console.log(session); if (status === 'authenticated') { router.push('/') } },[]) async function loggUser(e: FormEvent<HTMLFormElement>) { e.preventDefault(); const form = new FormData(e.currentTarget); const email = form.get("email"); const password = form.get("password"); const data = { email, password }; signIn('credentials',{...data, redirect: true, callbackUrl:"/"}).then((callback) => { if(callback?.error){ alert(callback.error) } if(callback?.ok){ alert("User logged in successful"); } }) } return ( <div className="flex min-h-full flex-1 flex-col justify-center px-6 py-12 lg:px-8"> <div className="sm:mx-auto sm:w-full sm:max-w-sm"> <img alt="Your Company" src="https://tailwindui.com/img/logos/mark.svg?color=indigo&shade=600" className="mx-auto h-10 w-auto" /> <h2 className="mt-10 text-center text-2xl font-bold leading-9 tracking-tight text-gray-900"> Sign in to your account </h2> </div> <div className="mt-10 sm:mx-auto sm:w-full sm:max-w-sm"> <form onSubmit={loggUser} className="space-y-6"> <div> <label htmlFor="email" className="block text-sm font-medium leading-6 text-gray-900" > Email address </label> <div className="mt-2"> <input id="email" name="email" type="email" required autoComplete="email" className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" /> </div> </div> <div> <div className="flex items-center justify-between"> <label htmlFor="password" className="block text-sm font-medium leading-6 text-gray-900" > Password </label> <div className="text-sm"> <a href="#" className="font-semibold text-indigo-600 hover:text-indigo-500" > Forgot password? </a> </div> </div> <div className="mt-2"> <input id="password" name="password" type="password" required autoComplete="current-password" className="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6" /> </div> </div> <div> <button type="submit" className="flex w-full justify-center rounded-md bg-indigo-600 px-3 py-1.5 text-sm font-semibold leading-6 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" > Sign in </button> </div> </form> <div className="w-full min-h-9 mt-10 relative"> <div className="absolute top-[50%] w-full h-[1px] bg-slate-300 "></div> <div className="bg-white flex justify-center items-center py-3 px-5 absolute top-[50%] left-[50%] translate-x-[-50%] translate-y-[-50%]"> <p>Or continue with</p> </div> </div> <div className="w-full flex justify-center items-center mt-5"> <button onClick={async () => await signIn('google',{redirect: true, callbackUrl:"/"})}> <div className="flex justify-center items-center gap-3 border-slate-200 border-[1px] px-16 py-1 rounded-lg"> <img className="w-8 h-8" src="https://cdn-icons-png.freepik.com/256/13170/13170545.png?semt=ais_hybrid" alt="Google" /> <p>Google</p> </div> </button> </div> <p className="mt-10 text-center text-sm text-gray-500"> Not a member?{" "} <a href="#" className="font-semibold leading-6 text-indigo-600 hover:text-indigo-500" > Start a 14 day free trial </a> </p> </div> </div> ); }; export default page;
UserSession组件
"use client"; import React from "react"; import { useSession } from "next-auth/react"; const UserSession = () => { const { data: session,status } = useSession(); console.log(status); return ( <div> <p>{session == null ? "not logged in" : "logged in"}</p> <p>{JSON.stringify(session)} --- {status}</p> </div> ); }; export default UserSession;
问题原因
- useEffect依赖缺失:
useEffect未监听session和status的变化,组件挂载时useSession处于loading状态,此时session为undefined,后续状态更新后useEffect不会重新执行,无法检测到authenticated状态。 - 页面保护方式有缺陷:仅用客户端组件做页面保护存在视觉延迟,用户可能短暂看到登录页内容后才跳转,且安全性不如服务端检查。
解决方案
方案1:修复客户端页面保护逻辑
修改login page.tsx中的useEffect,添加session、status和router作为依赖项,确保状态变化时重新执行跳转逻辑:
useEffect(() => { console.log(session); if (status === 'authenticated') { router.push('/') } }, [session, status, router])
方案2:服务端前置检查(推荐)
将/login页面改为服务端组件,提前在服务端检查session,避免客户端渲染的延迟和内容泄露:
// app/login/page.tsx import { getServerSession } from "next-auth/next"; import { authOptions } from "../api/auth/[...nextauth]/route"; // 替换为你的auth配置路径 import LoginClient from "./LoginClient"; // 将原登录UI拆分为客户端组件 import { redirect } from "next/navigation"; export default async function LoginPage() { const session = await getServerSession(authOptions); // 已登录则直接服务端重定向 if (session) { redirect("/"); } return <LoginClient />; }
原登录逻辑和UI部分迁移到LoginClient.tsx客户端组件中,这样用户访问登录页时,服务端会先完成session检查,直接重定向到首页,不会加载登录页内容。
补充说明
useSession在客户端组件中是异步获取session的,初始状态为loading,必须等待状态变为authenticated或unauthenticated后再执行逻辑。- 服务端检查session是更安全的实现方式,能避免客户端渲染带来的内容泄露风险。
内容的提问来源于stack exchange,提问作者Dimitar Kazakov
相关产品推荐
相关产品推荐

