使用NextJS、Supabase和React Query时出现调用栈溢出错误
NextJS跳转动态路由时栈溢出错误排查
错误信息
✓ Compiled in 1316ms (829 modules) ○ Compiling /lobby/[id] ... ✓ Compiled /lobby/[id] in 2s (1728 modules) ⨯ next/dist/compiled/react-server-dom-webpack/cjs/react-server-dom-webpack-server.edge.development.js (1469:16) @ logRecoverableError ⨯ RangeError: Maximum call stack size exceeded at stringify (<anonymous>) at stringify (<anonymous>) at stringify (<anonymous>) at stringify (<anonymous>) at stringify (<anonymous>) at stringify (<anonymous>) digest: "2761827113" 1467 | request.pendingChunks++; 1468 | prevKeyPath = request.nextChunkId++; > 1469 | task = logRecoverableError(request, key, task); | ^ 1470 | emitErrorChunk(request, prevKeyPath, task, key); 1471 | return parent 1472 | ? "$L" + prevKeyPath.toString(16)
相关代码
useFetch.ts
import { createClient } from "@/utils/supabase/client"; import { useMutation } from "@tanstack/react-query"; const supabase = createClient(); export const useFetchTeamLogin = () => { return useMutation({ mutationFn: async (teamName: string) => { try { const { data, error } = await supabase .from("teams") .select("teamToken") .eq("teamName", teamName) .single(); if (error) throw new Error(error.message); return data; } catch (err) { console.error("Mutation error:", err); throw err; } }, }); };
login.tsx
"use client"; import { useState } from "react"; import { useRouter } from "next/navigation"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from "@/components/ui/dialog"; import { useFetchTeamLogin } from "@/hooks/useFetch"; export default function Login() { const [teamName, setTeamName] = useState<string>(""); const { mutate: fetchTeamLogin, error, isError, isPending } = useFetchTeamLogin(); const router = useRouter(); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!teamName.trim()) { console.error("Team name cannot be empty."); return; } fetchTeamLogin(teamName.toLowerCase(), { onSuccess: (data) => { if (data?.teamToken) { router.push(`/lobby/${data.teamToken}`); } else { console.error("Team token is undefined."); } }, onError: (error) => { console.error("Error during login:", error.message); }, }); }; return ( <div className="relative"> <Dialog> <DialogTrigger asChild> <Button className="w-80 font-bold p-5 text-white bg-orange-500 hover:bg-orange-700 cursor-pointer transition-colors"> Join the Hack! </Button> </DialogTrigger> <DialogContent className="border-none bg-[#1e1e1e] w-[350px] rounded-md"> <DialogHeader> <DialogTitle className="text-2xl text-orange-600 font-pixelify text-left"> {isError ? <p className="text-red-500">{error?.message}</p> : "Login"} </DialogTitle> <DialogDescription className="text-md text-gray-600 text-left"> Please enter your correct information </DialogDescription> </DialogHeader> <form onSubmit={handleSubmit}> <Input type="text" placeholder="Enter Team Name" className="w-full text-gray-400 p-3 bg-transparent border border-gray-300 rounded-md mb-2" value={teamName} onChange={(e) => setTeamName(e.target.value)} required /> <p className="text-[10px] text-gray-300 text-right mb-3"> Input in lowercase with no whitespace character </p> <Button type="submit" disabled={isPending} size="lg" className="w-full bg-orange-500 text-white hover:bg-orange-600 transition-colors" > {isPending ? "Authenticating..." : "Login"} </Button> </form> </DialogContent> </Dialog> </div> ); }
lobby.tsx
export default function Lobby() { return ( <div className="flex items-center justify-center h-screen"> <h1 className="text-lg font-bold mb-4"> Welcome to the Lobby </h1> </div> ); }
问题原因及解决方案
1. Supabase返回数据存在循环引用
Supabase查询返回的data对象并非纯JSON结构,内部可能带有原型链属性或循环引用的元数据。当React Query缓存该对象,或组件尝试序列化它时,会触发JSON.stringify栈溢出。
解决: 修改useFetch.ts,只返回需要的纯数据字段:
// ... if (error) throw new Error(error.message); // 只提取需要的teamToken,避免返回带有内部引用的Supabase响应对象 return { teamToken: data.teamToken }; // ...
2. 动态路由文件结构错误
Next.js的动态路由要求文件命名为[param].tsx,且放在对应路由层级的文件夹下。比如访问/lobby/[teamToken],需要创建app/lobby/[teamToken]/page.tsx文件,而非根目录下的lobby.tsx。文件结构不匹配会导致路由匹配异常,触发无限渲染或序列化错误。
解决: 调整文件结构:
- 创建
app/lobby文件夹 - 在其中新建
[teamToken]/page.tsx,将原lobby.tsx的内容移入该文件
3. React Query错误对象的序列化问题
error对象可能带有循环引用(比如包含Supabase的原始错误对象),在客户端组件中渲染时,若父组件为服务端组件,可能触发序列化错误。
优化: 在useFetch.ts中捕获错误时,只抛出包含信息的纯Error对象,避免传递原始的Supabase错误:
// ... if (error) { // 仅提取错误信息,避免传递带有循环引用的原始错误对象 throw new Error(error.message); } // ...
内容的提问来源于stack exchange,提问作者Keiru
相关产品推荐
相关产品推荐

