如何解决Vercel部署后出现的调用栈大小超出限制错误
Google Docs克隆项目Vercel部署后栈溢出问题解决
问题描述
开发的Google Docs克隆项目本地运行完全正常,但部署到Vercel后,登录首页出现闪屏,随后触发「maximum call stack size exceeded」(栈溢出)错误。首页核心代码如下:
"use client"; import { useState, useEffect } from "react"; import { useSession } from "next-auth/react"; import Image from "next/image"; import { BsThreeDotsVertical } from "react-icons/bs"; import { Dialog, Button } from "@/lib/materialTailwind"; import axios from "axios"; import db from "@/firebase/config"; import { collection, onSnapshot, query } from "firebase/firestore"; import DocumentRow from "@/components/DocumentRow"; import Header from "@/components/Header"; import { Document } from "@/types"; export default function Home() { const { data: session } = useSession(); const [showModal, setShowModal] = useState(false); const [newDocumentTitle, setNewDocumentTitle] = useState(""); const [documents, setDocuments] = useState<Document[]>([]); const toggleModal = () => { setShowModal(!showModal); }; const createDcoument = () => { axios .post("/api/docs", { fileName: newDocumentTitle }) .then((res) => console.log(res.data)); setNewDocumentTitle(""); toggleModal(); }; const newDocModal = ( <Dialog size="xs" open={showModal} handler={toggleModal} className="bg-white shadow-none h-[200px] p-5 flex flex-col justify-between" > <input type="text" value={newDocumentTitle} onChange={(e) => setNewDocumentTitle(e.target.value)} placeholder="Enter name of document..." onKeyDown={(e) => e.key === "Enter" && createDcoument()} className="outline-none w-full" /> <div> <Button onClick={toggleModal} className="bg-transparent shadow-none hover:shadow-none text-blue-600" > CANCEL </Button> <Button color="blue" className="ml-4" onClick={createDcoument}> CREATE </Button> </div> </Dialog> ); useEffect(() => { const q = query(collection(db, `userDocs/${session?.user?.email}/docs`)); const unsubscribe = onSnapshot(q, (querySnapshot) => { let docArr: Document[] = []; querySnapshot.forEach((doc: any) => { docArr.push({ ...doc.data(), id: doc.id }); }); setDocuments(docArr); return () => unsubscribe(); }); }, [session?.user?.email]); return ( <div> <Header /> {newDocModal} <section className="bg-[#F0F3F4] pb-10 px-10"> <div className="max-w-3xl mx-auto"> <div className="py-6 flex justify-between items-center"> <h2 className="text-gray-700 text-lg">Create a new document</h2> <BsThreeDotsVertical size="1.2em" /> </div> <div> <Image src={ "https://ssl.gstatic.com/docs/templates/thumbnails/docs-blank-googlecolors.png" } alt="new Google Jock" height={150} width={150} onClick={toggleModal} className="border-2 rounded-md cursor-pointer hover:border hover:border-blue-500" /> <p className="mt-2 font-semibold text-sm text-gray-700">Blank</p> </div> </div> </section> <section> <div className="max-w-3xl mx-auto py-8"> <table className="table-auto w-full"> <thead> <tr> <th>Previous 30 Days</th> <th>Owned by anyone</th> <th>Last opened by me</th> </tr> </thead> <tbody> {documents.map((doc) => ( <DocumentRow key={doc.id} doc={doc} /> ))} </tbody> </table> </div> </section> </div> ); }
错误截图:
问题根源
核心问题出在useEffect的Firestore监听清理逻辑上:
- 你在
onSnapshot的回调函数内部返回了清理函数,而onSnapshot本身就会返回一个用于取消监听的unsubscribe函数。这种嵌套写法会导致旧的监听无法被正确取消,每次session?.user?.email变化时,新的监听被创建,但旧监听仍在运行,触发无限的状态更新循环,最终导致栈溢出。 - 另外,没有判断
session是否存在就创建Firestore查询,可能导致空值访问,加剧渲染异常。
解决步骤
1. 修正useEffect的清理逻辑
将unsubscribe直接作为useEffect的返回值,确保每次依赖变化时,旧监听被正确取消:
useEffect(() => { // 先判断session是否有效,避免空值访问 if (!session?.user?.email) return; const q = query(collection(db, `userDocs/${session.user.email}/docs`)); const unsubscribe = onSnapshot(q, (querySnapshot) => { const docArr: Document[] = []; querySnapshot.forEach((doc) => { docArr.push({ ...doc.data(), id: doc.id }); }); setDocuments(docArr); }); // 直接返回unsubscribe作为清理函数 return unsubscribe; }, [session?.user?.email]);
2. 修正函数拼写错误
将createDcoument改为createDocument(注意字母o),避免后续调用时的潜在问题。
3. 可选:优化模态框渲染
可以将newDocModal改为组件内的JSX直接渲染,避免不必要的组件实例创建:
// 替换原本的newDocModal变量,直接在return里渲染 {showModal && ( <Dialog size="xs" open={showModal} handler={toggleModal} className="bg-white shadow-none h-[200px] p-5 flex flex-col justify-between" > {/* 模态框内容不变 */} </Dialog> )}
为什么仅生产环境出现问题?
- Next.js生产构建特性:生产环境下代码会被压缩、优化,开发环境的容错机制(如警告而非错误)被移除,原本隐藏的逻辑错误直接触发崩溃。
- Strict Mode差异:虽然开发环境也有Strict Mode,但本地Firestore可能使用缓存,组件渲染时序不同,没有触发无限循环;而生产环境中实时监听的触发更频繁,循环调用更快达到栈上限。
- Vercel执行上下文:生产环境的资源限制和执行环境与本地不同,使得栈溢出的阈值更快被触发。
内容的提问来源于stack exchange,提问作者Jevon Cochran
相关产品推荐
相关产品推荐

