Vercel部署NextAuth会话校验失败:本地正常线上504错误排查
我开发了一个博客,需要在用户访问创建文章页面时校验其管理员身份。该功能在本地运行正常,但部署到Vercel后出现504错误,日志无有效信息。我已将NEXTAUTH_URL改为Vercel提供的HTTPS地址。
相关代码
NewPostPage 代码
const NewPostPage = (props) => { if (props.messageError) { console.log("Error auth: " + props.messageError); } return ( <div className={classes.pageContainer}> <NewPostForm /> </div> ); }; export default NewPostPage export const getServerSideProps = async (context) => { try { return await requireAuthentification(context, "admin", ({ session }) => { return { props: { session, messageError: null }, } }) } catch (error) { return { props: { messageError: error } } } }
requireAuthentification 函数代码
const requireAuthentification = async (context, levelAcces = "public", callback) => { const session = await unstable_getServerSession(context.req, context.res, authOptions) if (!session) { return { redirect: { destination: '/', permanent: false, } } } else { let user = []; const q = query(collection(db, "users"), where("email", "==", session.user.email)); const querySnapshot = await getDocs(q); user = querySnapshot.docs[0].data(); if (user.level !== levelAcces) { return { redirect: { destination: '/', permanent: false, } } } } return callback({ session }); } export default requireAuthentification
移除getServerSideProps后页面可正常访问但无路由安全校验,我怀疑问题出在unstable_getServerSession,使用GetSession也有同样问题,Vercel日志无法提供有效调试信息。请问为何本地正常线上异常?如何调试?
本地正常、线上504的核心原因
Firebase请求超时
服务端直接请求Firebase查询用户权限时,Vercel服务器与Firebase的跨区域网络延迟、防火墙限制等,会导致请求超时触发504。本地环境网络链路短,不会出现这类问题。NextAuth配置不全
即使改了NEXTAUTH_URL,线上环境还需显式配置NEXTAUTH_SECRET(本地可自动生成,线上必须手动设置),否则unstable_getServerSession可能无法正常解析session。另外authOptions里的依赖配置(比如回调函数)若依赖本地环境变量,线上也会失效。不稳定API兼容性问题
unstable_getServerSession标记为测试API,可能在Vercel的Node.js运行环境中存在隐性bug,换用稳定版getServerSession(NextAuth v4.23+推荐)可规避这类问题。
调试与修复方案
添加精准日志定位卡点
在requireAuthentification关键步骤插入日志,查看Vercel的函数日志(而非普通应用日志)定位问题:const requireAuthentification = async (context, levelAcces = "public", callback) => { console.log("[Auth] Start session fetch"); const session = await unstable_getServerSession(context.req, context.res, authOptions) console.log("[Auth] Session fetched:", session ? "success" : "null"); if (!session) { console.log("[Auth] No session, redirect"); return { redirect: { destination: '/', permanent: false } } } console.log("[Auth] Fetch user from Firebase:", session.user.email); const q = query(collection(db, "users"), where("email", "==", session.user.email)); // 给Firebase请求加超时控制 const querySnapshot = await Promise.race([ getDocs(q), new Promise((_, reject) => setTimeout(() => reject(new Error("Firebase query timeout")), 5000)) ]); console.log("[Auth] Firebase query result count:", querySnapshot.docs.length); const user = querySnapshot.docs[0].data(); console.log("[Auth] User level:", user.level); if (user.level !== levelAcces) { console.log("[Auth] Level mismatch, redirect"); return { redirect: { destination: '/', permanent: false } } } console.log("[Auth] Auth passed"); return callback({ session }); }优化Firebase访问逻辑
- 检查Firebase IAM权限:确保服务端密钥拥有
users集合的读取权限,避免因权限不足导致请求卡住。 - 缓存用户权限到NextAuth Session:在
authOptions的回调中把用户level注入session,避免每次请求都查询Firebase:
之后// authOptions配置示例 export const authOptions = { session: { strategy: "jwt" }, callbacks: { async jwt({ token, user }) { if (user) { const q = query(collection(db, "users"), where("email", "==", user.email)); const querySnapshot = await getDocs(q); token.level = querySnapshot.docs[0].data().level; } return token; }, async session({ session, token }) { session.user.level = token.level; return session; } } }requireAuthentification可直接从session.user.level校验,无需再请求Firebase。
- 检查Firebase IAM权限:确保服务端密钥拥有
调整Vercel函数超时
Vercel Serverless函数默认超时10秒,可在vercel.json中调整最大超时(上限15秒):{ "functions": { "pages/api/**/*.js": { "maxDuration": 15 } } }
内容的提问来源于stack exchange,提问作者Romano

