Next.js 15项目出现JavaScript堆内存溢出致命错误求助
问题现象
Next.js 15项目频繁触发FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory错误,每日重启服务后仍会复现。网站大部分内容通过SSR加载,基于会话进行身份验证。
错误日志
2025-09-21T18:50:49: [905257:0x25f7a690] 48491672 ms: Scavenge 1966.7 (2083.4) -> 1960.5 (2083.4) MB, 9.7 / 0.1 ms (average mu = 0.948, current mu = 0.947) allocation failure;
2025-09-21T18:50:49: [905257:0x25f7a690] 48493391 ms: Scavenge 1965.9 (2083.4) -> 1960.9 (2083.4) MB, 8.5 / 0.0 ms (average mu = 0.948, current mu = 0.947) task;
2025-09-21T18:50:49: [905257:0x25f7a690] 48493878 ms: Scavenge 1967.2 (2083.4) -> 1962.4 (2099.4) MB, 16.0 / 0.1 ms (average mu = 0.948, current mu = 0.947) task;<--- JS stacktrace --->
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
核心代码片段
app/middleware.ts
import { NextResponse } from "next/server"; import type { NextRequest } from "next/server"; export async function middleware(req: NextRequest) { const pathname = req.nextUrl.pathname; const url = req.nextUrl.clone(); if (pathname.match(/^\/brand\/[^\/]+\/[^\/]+\/[^\/]+\/?$/)) { const parts = pathname.split('/'); const refnum = parts[4]; if (/\%20|[. ~A-Z]/.test(refnum)) { const cleanRefnum = refnum .replace(/%20/g, '-') .replace(/[. ~]/g, '-') .toLowerCase() .replace(/-+/g, '-') .replace(/^-|-$/g, ''); parts[4] = cleanRefnum; url.pathname = parts.join('/'); return NextResponse.redirect(url, 301); } } if ( pathname.startsWith('/new-products/') ) { return NextResponse.redirect(new URL('/products/', req.url)) } const response = NextResponse.next(); try{ const res = await fetch(process.env.NEXT_PUBLIC_API_URL + "api/customer/auth/info", { cache:"no-cache", headers: { cookie: req.headers.get("cookie") || "", }, credentials:"include" }); const sessionData = await res.json(); response.headers.set("x-session", JSON.stringify(sessionData.session || {})); }catch(err){ console.error("Middleware Error : "+err.message) response.headers.set("x-session", JSON.stringify({})); } return response; } export const config = { matcher: [ '/((?!api|_next/static|_next/image|favicon\\.ico|.*\\.(?:ico|png|jpg|jpeg|webmanifest)$).*)' ], };
首页page.ts
import { Metadata } from "next" import { Suspense } from "react" import { headers } from "next/headers" import { PageDataType } from "@/types" import axiosInstance from "@/lib/axiosInstance" import PageContent from "./(home)/_components/page-content" export const metadata: Metadata = { title: "Random Website", description: "This is a test description", alternates: { canonical: `${process.env.BASEURL}` } } export default async function Home() { const sessionData = JSON.parse(await (await headers()).get("x-session") || "{}") let pageData: PageDataType = {} try { const [homeResponse, faqResponse, blogResponse, testinomialResponse] = await Promise.all([ axiosInstance.post("/site/data", {}).catch((error) => ({ error, data: null })), axiosInstance.post("/faq/list", { }).catch((error) => ({ error, data: null })), axiosInstance.get("https://blog.mysite.sg/wp-json/wp/v2/posts?page=1&per_page=6", {}).catch((error) => ({ error, data: null })), axiosInstance.post("/ratings/list", {"username": "website"}) ]) pageData = { ...homeResponse.data, ...faqResponse.data, blogData: blogResponse.data, testinomialData: testinomialResponse.data.ratings } } catch (error) { console.log("Error fetching data:", error) } return ( <Suspense> <PageContent pageData={pageData} sessionData={sessionData} /> </Suspense> ) }
排查分析
Middleware 全局请求累积内存占用
- Middleware的matcher覆盖了几乎所有非静态资源请求,每次请求都会发起
/api/customer/auth/info的无缓存fetch调用,高并发场景下会导致大量请求实例堆积,内存无法及时回收。 - 每次将session数据序列化为JSON存入响应头,若session数据体积较大,频繁的序列化操作会进一步增加内存开销。
- Middleware的matcher覆盖了几乎所有非静态资源请求,每次请求都会发起
SSR数据加载的内存泄漏风险
- 首页同时发起4个API请求,若
axiosInstance未配置请求取消机制,当请求超时或页面被中断时,未完成的请求实例会占用内存无法释放。 - 若接口返回的数据量过大(如博客、评价数据),SSR阶段每个请求都会加载完整数据,长期累积会导致堆内存耗尽。
- 首页同时发起4个API请求,若
Node.js 默认堆内存限制不足
- 从日志看堆内存已接近2GB,超过Node.js默认的堆内存限制(约1.4GB),这会直接触发内存溢出错误。
修复建议
优化Middleware逻辑
- 为
/api/customer/auth/info请求添加缓存策略,比如设置cache: "force-cache"并配置revalidate,减少重复请求;或通过NextResponse.cache()设置缓存头,避免每次请求都重新获取session。 - 缩小matcher范围,仅匹配需要会话验证的路由,避免全局触发Middleware逻辑。
- 为
优化SSR数据获取
- 检查接口返回数据量,对大体积数据做分页处理,只加载当前页面所需内容。
- 为axios请求添加
AbortController,在组件卸载或请求超时后取消未完成的请求,释放内存。 - 将非关键数据(如博客、评价)改为客户端组件加载,降低SSR阶段的内存占用。
调整Node.js堆内存限制
- 在启动命令中添加
--max-old-space-size参数,例如next start --max-old-space-size=4096,将堆内存限制提升至4GB,临时缓解内存不足问题(需配合代码优化才能彻底解决)。
- 在启动命令中添加
内容的提问来源于stack exchange,提问作者Ajith

