NextAuth登录时出现TypeError: fetch failed问题求助
问题描述
每次通过任意登录提供商登录时,都会触发以下错误:
[next-auth][error][CLIENT_FETCH_ERROR] https://next-auth.js.org/errors#client_fetch_error fetch failed { error: { message: 'fetch failed', stack: 'TypeError: fetch failed\n' + ' at Object.fetch (node:internal/deps/undici/undici:11457:11)\n' + ' at process.processTicksAndRejections (node:internal/process/task_queues:95:5)', name: 'TypeError' }, url: 'http://localhost:3000/api/auth/session', message: 'fetch failed' }
登录完成后无法重定向至首页/,页面无法识别已登录状态,但直接访问http://localhost:3000/api/auth/session能看到正确的会话信息。问题出现在执行next build之后,怀疑和getServerSideProps有关。
相关配置代码
.env 文件
NEXTAUTH_URL=http://localhost:3000 EMAIL_FROM=noreply@example.com EMAIL_SERVER=smtp: GOOGLE_CLIENT_ID= GOOGLE_CLIENT_SECRET= DISCORD_CLIENT_ID= DISCORD_CLIENT_SECRET= AUTH_SECRET=myPassword
[...nextauth].js 文件
export const authOptions = { secret: process.env.AUTH_SECRET, providers: [ CredentialsProvider({ id: 'credentials', name: 'credentials', credentials: { email: { label: "E-mail", type: "text", placeholder: "youremail@mail.com" }, password: { label: "Password", type: "password", placeholder: "password" }, }, authorize: async (credentials) => { const user = await prisma.user.findUnique({ where: { email: credentials.email } }); if (!user) return null; return user; }, }), EmailProvider({ server: process.env.EMAIL_SERVER, from: process.env.EMAIL_FROM }), GoogleProvider({ clientId: process.env.GOOGLE_CLIENT_ID, clientSecret: process.env.GOOGLE_CLIENT_SECRET }), DiscordProvider({ clientId: process.env.DISCORD_CLIENT_ID, clientSecret: process.env.DISCORD_CLIENT_SECRET }), ], adapter: PrismaAdapter(prisma), session: { strategy: 'jwt', }, maxAge: 30 * 24 * 60 * 60, }
pages/index.js 文件
import { Inter } from "@next/font/google"; import { useEffect } from "react"; import {useDispatch} from "react-redux"; import {setUserState} from "@//tools/redux/userSlice"; import { getSession, useSession, signIn, signOut } from "next-auth/react"; const inter = Inter({ subsets: ["latin"] }); export default function Home(props: any) { const { data: session, status } = useSession(); const dispatch = useDispatch() useEffect(() => { (async () => { if (session) dispatch(setUserState(session.user)); })() }, []); return ( <div className="flex justify-center items-center flex-col my-32"> {!props.user && <> <h1 className="text-5xl font-bold text-gray-500 ">You are not logged in!</h1> <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full" onClick={() => signIn()}>Sign in</button> </> } {props.user && <> <h1 className="text-5xl font-bold text-gray-500 ">Welcome, {props.user?.email ?? 'User'}</h1> <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full"onClick={() => signOut()}>Sign out</button> </> } </div> ); } export async function getServerSideProps(ctx: any) { const session = await getSession(ctx); if (!session) { return { props: {} }; } const { user } = session; return { props: { user } }; }
解决方案
1. 修正环境变量配置
- NEXTAUTH_URL:静态构建后客户端可能无法正确解析本地地址,生产构建时建议设置为实际域名,或确保启动服务时的HOST/PORT与该配置完全一致;本地测试可保留,但要注意
next start启动的服务端口必须是3000。 - AUTH_SECRET:不要用
myPassword这种简单字符串,执行openssl rand -hex 32生成一个安全的随机密钥替换。
2. 修复getServerSideProps会话逻辑
当前逻辑在无会话时返回空props,容易和客户端useSession的加载状态冲突,导致页面误判登录状态。修改如下:
export async function getServerSideProps(ctx: any) { const session = await getSession(ctx); // 统一返回session对象,不管是否登录 return { props: { session: session || null } }; }
同时修改首页组件,统一使用服务端+客户端的会话状态,避免混用:
export default function Home({ session: serverSession }) { const { data: clientSession, status } = useSession({ required: true, onUnauthenticated() { // 未登录时可添加跳转逻辑 } }); const dispatch = useDispatch() useEffect(() => { const currentSession = serverSession || clientSession; if (currentSession) dispatch(setUserState(currentSession.user)); }, [serverSession, clientSession, dispatch]); return ( <div className="flex justify-center items-center flex-col my-32"> {status === 'loading' ? ( <p>加载中...</p> ) : !serverSession && !clientSession ? ( <> <h1 className="text-5xl font-bold text-gray-500 ">你未登录!</h1> <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full" onClick={() => signIn()}>登录</button> </> ) : ( <> <h1 className="text-5xl font-bold text-gray-500 ">欢迎,{(serverSession || clientSession).user?.email ?? '用户'}</h1> <button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full" onClick={() => signOut()}>退出登录</button> </> )} </div> ); }
3. 修正NextAuth会话配置
maxAge属性位置错误,应该放到session配置对象内,否则不会生效:
export const authOptions = { secret: process.env.AUTH_SECRET, providers: [/* ... */], adapter: PrismaAdapter(prisma), session: { strategy: 'jwt', maxAge: 30 * 24 * 60 * 60, // 移到此处 }, }
4. 规范构建后服务启动
执行next build后,必须用next start启动服务,不能用next dev。启动时检查控制台输出的服务地址,确保和NEXTAUTH_URL一致。
5. 补全CredentialsProvider密码验证
当前授权逻辑只检查用户存在,未验证密码,既不安全也可能导致会话生成异常(假设用户表存储哈希密码):
import bcrypt from 'bcryptjs'; // 替换原authorize函数 authorize: async (credentials) => { const user = await prisma.user.findUnique({ where: { email: credentials.email } }); if (!user) return null; // 验证密码哈希 const isPasswordValid = await bcrypt.compare(credentials.password, user.password); if (!isPasswordValid) return null; return user; },
内容的提问来源于stack exchange,提问作者Max
相关产品推荐
相关产品推荐

