NextJS客户端调用MongoDB Server Action触发栈溢出错误排查
解决帖子回复组件无限滚动的栈溢出及第二页加载失败问题
问题分析
开发帖子回复展示组件时,尝试用react-intersection-observer实现无限滚动出现Maximum call stack size exceeded错误,改为按钮触发加载第二页后仍无法正常获取数据。核心问题集中在Server Action的调用方式、客户端状态管理及props传递三个方面。
修复步骤
1. 标记Server Action为服务端执行
Next.js中Server Action必须显式声明'use server'指令,否则客户端会将其当作本地函数执行,引发调用栈溢出或请求失败。修改你的Server Action代码:
'use server'; // 必须添加此行,标记为服务端执行函数 export async function getAnswers(params: GetAnswersParams) { try { connectToDatabase(); const { questionId, sortBy, page = 1, pageSize = 10 } = params; const skipAmount = (page - 1) * pageSize; let sortOptions = {}; switch (sortBy) { case 'highestUpvotes': sortOptions = { upvotes: -1 }; break; case 'lowestUpvotes': sortOptions = { upvotes: 1 }; break; case 'recent': sortOptions = { createdAt: -1 }; break; case 'old': sortOptions = { createdAt: 1 }; break; default: break; } const answers = await Answer.find({ question: questionId }) .populate('author', '_id clerkId name picture') .sort(sortOptions) .skip(skipAmount) .limit(pageSize); const totalAnswers = await Answer.countDocuments({ question: questionId }); const totalPages = Math.ceil(totalAnswers / pageSize); const isNext = totalAnswers > skipAmount + answers.length; return { answers, isNext, totalPages }; } catch (error) { console.log(error); throw error; } }
2. 客户端维护页码状态,避免硬编码
原客户端代码中硬编码page:2,只能重复加载第二页,且未合并数据。修改LoadMoreAnswers组件,添加页码跟踪状态:
const LoadMoreAnswers = ({ initialAnswers, questionId, userId, filter }: Props) => { const [allAnswers, setAllAnswers] = useState(JSON.parse(initialAnswers)); const [currentPage, setCurrentPage] = useState(1); const [isLoading, setIsLoading] = useState(false); const loadMoreAnswers = async () => { if (isLoading) return; // 防止重复点击触发请求 setIsLoading(true); try { const nextPage = currentPage + 1; const { answers } = await getAnswers({ questionId, // 直接使用props传递的questionId page: nextPage, sortBy: filter || 'recent', // 使用传入的筛选条件,而非硬编码 }); // 合并新旧回复数据 setAllAnswers(prevAnswers => [...prevAnswers, ...answers]); setCurrentPage(nextPage); } catch (error) { console.error('Error loading more answers:', error); } finally { setIsLoading(false); } }; return ( <> {allAnswers.map((answer) => ( <article key={answer._id} className='light-border border-b py-10'> <div className='mb-8 flex flex-col-reverse justify-between gap-5 sm:flex-row sm:items-center sm:gap-2'> <Link href={`/profile/${answer.author.clerkId}`} className='flex flex-1 items-start gap-1 sm:items-center' > <Image src={answer.author.picture} alt='profile picture' width={18} height={18} className='rounded-full object-cover max-sm:mt-0.5' /> <div className='flex flex-col sm:flex-row sm:items-center'> <p className='body-semibold text-dark300_light700'>{answer.author.name}</p> <p className='small-regular text-dark400_light500 mt-0.5 line-clamp-1'> <span className='mx-0.5 max-sm-hidden'>•</span> {/* answered {getTimestamp(answer.createdAt)} */} </p> </div> </Link> <div className='flex justify-end'> <Votes type='Answer' itemId={JSON.stringify(answer._id)} userId={userId} upvotes={answer.upvotes.length} hasupVoted={answer.upvotes.includes(userId)} downvotes={answer.downvotes.length} hasdownVoted={answer.downvotes.includes(userId)} /> </div> </div> <ParseHTML data={answer.content} /> </article> ))} <Button onClick={loadMoreAnswers} disabled={isLoading}> {isLoading ? '加载中...' : '加载更多'} </Button> </> ); }; export default LoadMoreAnswers;
3. 优化Server到Client的Props传递
原代码中对questionId进行了不必要的JSON.stringify和JSON.parse,可能导致数据格式异常。修改AllAnswers组件,直接传递原始props:
const AllAnswers = async ({ questionId, userId, totalAnswers, page, filter }: Props) => { const { answers, isNext, totalPages } = await getAnswers({ questionId, page: page ? +page : 1, sortBy: filter, }); return ( <div className='mt-11'> <div className='flex items-center justify-between'> <h3 className='primary-text-gradient'>{`${totalAnswers} ${totalAnswers === 1 ? 'Answer' : 'Answers'}`}</h3> <Filter filters={AnswerFilters} /> </div> <LoadMoreAnswers initialAnswers={JSON.stringify(answers)} questionId={questionId} // 直接传递,无需序列化 userId={userId} filter={filter} /> </div> ); }; export default AllAnswers;
4. 修复原无限滚动的栈溢出问题(可选)
如果之后要恢复滚动加载,需通过加载状态防止重复触发请求:
import { useInView } from 'react-intersection-observer'; const LoadMoreAnswers = ({ initialAnswers, questionId, userId, filter }: Props) => { // ...其他状态 const { ref, inView } = useInView({ threshold: 0.1 }); useEffect(() => { if (inView && !isLoading) { loadMoreAnswers(); } }, [inView, isLoading]); return ( <> {/* ...回复列表渲染 */} <div ref={ref} className='mt-11 flex w-full items-center justify-center'> <Button onClick={loadMoreAnswers} disabled={isLoading}> {isLoading ? '加载中...' : '加载更多'} </Button> </div> </> ); };
关键说明
- Server Action必须加
'use server',否则客户端无法正确发起服务端请求。 - 客户端需维护页码状态,确保每次加载下一页而非重复加载同一页。
- 避免不必要的JSON序列化/反序列化,防止数据格式错误或性能损耗。
内容的提问来源于stack exchange,提问作者Andreul-
相关产品推荐
相关产品推荐

