
在 Next.js 应用中,使用 fetch 调用 Route Handler 并执行 router.push() 后,浏览器标签页的加载图标仍持续旋转,通常是因为未显式终止客户端加载状态,导致浏览器误判导航未完成。
在 next.js 应用中,使用 `fetch` 调用 route handler 并执行 `router.push()` 后,浏览器标签页的加载图标仍持续旋转,通常是因为未显式终止客户端加载状态,导致浏览器误判导航未完成。
这个问题表面看是“UI 卡在加载态”,实则源于浏览器对 导航生命周期 的判断逻辑:当页面通过客户端导航(如 router.push)跳转时,若发起请求的组件仍处于 loading = true 状态,且该状态未被及时重置,Next.js 的客户端路由器可能无法准确标记前一个路由的“退出完成”,从而让浏览器维持挂起的加载指示器(即标签页右上角的旋转图标)。更严重的是,在 Vercel 等 Serverless 平台部署时,这种未终结的状态还可能被误判为函数长时间运行,触发超时错误(Serverless function timed out)。
关键修复点在于:无论请求成功或失败,只要服务端响应已接收,就必须立即清除加载状态。你当前的代码仅在 data.status === 200 分支中调用了 router.push("/"),但遗漏了 setLoading(false) —— 这导致即使重定向发生,loading 状态仍为 true,干扰了 Next.js 的路由状态同步机制。
以下是修正后的 handleSubmit 实现(含健壮性增强):
const handleSubmit = async () => {
setLoading(true);
try {
const res = await fetch("/api/saveRecipe", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
title,
time,
time_unit: timeUnit,
serves,
ingredients,
instructions,
}),
});
const data = await res.json();
// ✅ 关键:无论成功或失败,响应到达后立即关闭 loading 状态
setLoading(false);
if (res.ok && data.status === 200) {
router.push("/");
} else {
console.error("Save failed:", data.error);
setError(true);
}
} catch (e) {
setLoading(false); // ✅ 异常路径同样需重置
console.error("Network or parsing error:", e);
setError(true);
}
};
此外,建议同步优化你的 Route Handler,确保始终返回明确的 HTTP 状态码和结构化响应:
// app/api/saveRecipe/route.ts
import { createRouteHandlerClient } from "@supabase/auth-helpers-nextjs";
import { cookies } from "next/headers";
import { NextResponse } from "next/server";
export const dynamic = "force-dynamic";
export async function POST(req: Request) {
const supabase = createRouteHandlerClient({ cookies });
try {
const body = await req.json();
const { error } = await supabase.from("Recipes").insert(body);
if (error) {
console.error("Supabase insert error:", error);
return NextResponse.json(
{ error: "Failed to save recipe", status: 500 },
{ status: 500 }
);
}
// ✅ 显式返回 200 状态码(避免默认 200 但响应体不一致)
return NextResponse.json(
{ status: 200, message: "Recipe saved successfully" },
{ status: 200 }
);
} catch (error) {
console.error("Unexpected error in route handler:", error);
return NextResponse.json(
{ error: "Internal server error", status: 500 },
{ status: 500 }
);
}
}
注意事项总结:
- setLoading(false) 必须放在 try 块内、所有分支(包括 if/else 和 catch)中,确保状态终将归零;
- 避免仅依赖 router.push 触发加载结束 —— 它是导航动作,不是状态管理指令;
- Route Handler 返回的 status 字段应与 HTTP 状态码一致,便于前端统一判断;
- 生产环境务必添加 console.error 日志,便于快速定位超时根源(如数据库连接阻塞、未 await 的 Promise 等)。
遵循以上实践,即可彻底解决浏览器加载图标悬停问题,并提升应用在 Serverless 环境下的稳定性与可观测性。











