vercel / vercel/next.js

Memory leak with axios and AbortSignal in middleware in next >=15.4

Open
#84,884 18 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Middleware
Dominant language
JavaScript
Stars
142k
Forks
32.4k
Avg merge
2d 14h
Merged PRs (30d)
351

Description

Link to the code that reproduces this issue

https://github.com/liammcateer/next-abort-signal-memory-leak-reproduction

To Reproduce
  1. Run npm install
  2. Build the next app: npm run build
  3. Run the mock server in a separate terminal window: node mockServer.js
  4. Start the next app: npm run start
  5. Make many requests (I do 1,000) to a single page
  6. Take a heap snapshot using kill -USR2 <next-server PID>. You can find the PID by running ps aux | grep "next-server (v15.5.5)"
  7. Open the heap snapshot in chrome dev tools and look for increasing memory
  8. Repeat steps 5-7 with all other pages.

You will notice that /middleware/axios is the only one that increases memory. You can also verify this by leaving the server running for a little bit and noticing that the memory is never returned.

Current vs. Expected behavior

I expect that fetching an API with axios using the AbortSignal should not cause a memory leak.

Provide environment information
Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 24.3.0: Thu Jan  2 20:24:16 PST 2025; root:xnu-11215.81.4~3/RELEASE_ARM64_T6000
  Available memory (MB): 32768
  Available CPU cores: 8
Binaries:
  Node: 22.14.0
  npm: 10.9.2
  Yarn: 1.22.22
  pnpm: N/A
Relevant Packages:
  next: 15.5.5 // No Next.js version data was found.
  eslint-config-next: 15.5.5
  react: 19.1.0
  react-dom: 19.1.0
  typescript: 5.9.3
Next.js Config:
  output: N/A
Which area(s) are affected? (Select all that apply)

Middleware

Which stage(s) are affected? (Select all that apply)

next start (local), Other (Deployed)

Additional context

Tested against different canary releases, the first one that introduced the issue is 15.4.0-canary.95. 15.4.0-canary.94 does not have the issue.

When using axios to call an API inside middlware.ts with an AbortSignal, a memory leak appears.
This code will reproduce the issue:

export async function middleware(req: NextRequest) {
  await axios.get('https://<some-api>/', {
    signal: AbortSignal.timeout(1000),
    adapter: 'fetch',
  });

  return NextResponse.next();
}

If the AbortSignal is removed, or fetch is used instead of axios with an AbortSignal, the issue goes away.

In my reproduction, I called the same api using fetch and axios in both middleware.ts and page.tsx. The issue is only present when axios is used in middleware.ts. Each request was made 1000 times.
Here is a screenshot of various heap snapshots taken after each test. You can see that when axios is used in middleware, the problem presents. I also took another snapshot 30 seconds later, and the memory was still not released. I have also taken some snapshots (not shown) where I left the app for 15 mins, and the memory was still not released.
Image
This screenshot shows quite a few (over 2000) {signal,abort} objects hanging around
Image

I understand that calling APIs in middleware is an anti-pattern, however, this used to work previously, so should be reported as a bug. I also understand that there is a workaround to use fetch instead of axios. Our generated API fetching code uses axios, so our team would need to spend some effort to migrate away from axios. We are using axios with the fetch adapter.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the linked reproduction and its middleware.ts, running the documented npm install, build, mockServer.js, and start commands. Compare the behavior between 15.4.0-canary.94 and 15.4.0-canary.95, then repeat the heap-snapshot procedure for the axios fetch adapter and AbortSignal case. Done means repeated middleware requests no longer retain the growing signal objects or unreleased memory.

Written by the indexing model from the issue text.

Assessment

Tech stack
nextjs, node.js, typescript
Domain
backend, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.