nextjs - ✅(Solved) Fix Memory leak with axios and AbortSignal in middleware in next >=15.4 [1 pull requests, 13 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#84884Fetched 2026-04-08 02:18:04
View on GitHub
Comments
13
Participants
3
Timeline
26
Reactions
1
Timeline (top)
commented ×13subscribed ×7mentioned ×2issue_type_added ×1

Fix Action

Fix / Workaround

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.

PR fix notes

PR #1: Security hardening, PII redaction, and icon cleanup

Description (problem / solution / changelog)

Summary

  • CSRF protection & security headers: Origin validation on mutation API routes, CSP, HSTS, X-Frame-Options, and Referrer-Policy added to proxy
  • PII redaction overhaul: New ChatPIIRedactor class wraps chat tool results with privacy placeholders before the LLM sees donor names; extended pii-redaction.ts with phone/zip fields and regex-based fallback for emails and phone numbers in free-text; chat history storage now redacts PII patterns
  • API error sanitization: All API routes now log error.message server-side but return generic error messages to clients — prevents leaking internal details
  • Interaction org scoping fix: interactions table has no org_id column — dashboard insights, smart actions, and digest queries now correctly scope via donors!inner(org_id) join
  • Shared recalcDonorTotals utility: Extracted duplicated logic from 4 locations into frontend/lib/recalc-donor-totals.ts
  • Icon migration: All @tabler/icons-react imports replaced with lucide-react for consistency
  • UI polish: Standardized strokeWidth={1.5} on icons, shadow-sm across components, dark mode on global error page

Test plan

  • Verify app loads and navigates without errors
  • Test chat agent — confirm donor names appear correctly (placeholder replacement works)
  • Confirm API error responses no longer include details / error.message in client responses
  • Test CSRF: mutation API calls from same origin succeed; calls without Origin header are blocked
  • Verify dashboard insights and smart actions load correctly (interaction scoping fix)
  • Check icons render correctly across all affected components
  • Test both light and dark mode

🤖 Generated with Claude Code

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

Summary by CodeRabbit

  • New Features

    • Added chat PII redaction with placeholder anonymization
    • New transcription and route-planning endpoints
    • Reports can include creator and share info
  • Bug Fixes

    • Scoped dashboard queries to the correct organization to prevent cross-org data leakage
  • Security

    • CSRF/origin validation for state-changing API requests
    • API errors now return generic messages; server-side error logging improved
  • UI/UX

    • Migrated icon library for consistency
    • Dark-mode tweaks and refined shadows/stroke styling
  • Chores

    • Donor total recalculation moved to a shared utility
    • Code formatting standardization
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Changed files

  • frontend/app/actions/donations.ts (modified, +2/-26)
  • frontend/app/actions/import.ts (modified, +2/-23)
  • frontend/app/api/auth/link-pending-org/route.ts (modified, +2/-1)
  • frontend/app/api/chat/history/route.ts (modified, +4/-2)
  • frontend/app/api/chat/route.ts (modified, +21/-8)
  • frontend/app/api/dashboard/insights/route.ts (modified, +6/-6)
  • frontend/app/api/dashboard/smart-actions/route.ts (modified, +6/-4)
  • frontend/app/api/donations/options/route.ts (modified, +2/-1)
  • frontend/app/api/donations/recent/route.ts (modified, +2/-1)
  • frontend/app/api/donations/route.ts (modified, +4/-19)
  • frontend/app/api/donations/trend/route.ts (modified, +2/-1)
  • frontend/app/api/donors/[id]/insights/route.ts (modified, +2/-0)
  • frontend/app/api/donors/[id]/route.ts (modified, +2/-1)
  • frontend/app/api/donors/geocode-backfill/route.ts (modified, +2/-1)
  • frontend/app/api/donors/route.ts (modified, +4/-2)
  • frontend/app/api/donors/states/route.ts (modified, +2/-1)
  • frontend/app/api/feedback/route.ts (modified, +2/-1)
  • frontend/app/api/interactions/[id]/toggle/route.ts (modified, +2/-1)
  • frontend/app/api/interactions/route.ts (modified, +9/-2)
  • frontend/app/api/organization/logo/route.ts (modified, +1/-1)
  • frontend/app/api/pipeline/opportunities/route.ts (modified, +2/-1)
  • frontend/app/api/reports/[id]/route.ts (modified, +40/-4)
  • frontend/app/api/reports/create/route.ts (modified, +4/-2)
  • frontend/app/api/reports/generate/route.ts (modified, +2/-1)
  • frontend/app/api/reports/route.ts (modified, +147/-2)
  • frontend/app/api/reports/save/route.ts (modified, +2/-1)
  • frontend/app/api/reports/schema/route.ts (modified, +1/-1)
  • frontend/app/api/routes/plan/route.ts (added, +94/-0)
  • frontend/app/api/tags/route.ts (modified, +2/-1)
  • frontend/app/api/tasks/[id]/route.ts (modified, +4/-2)
  • frontend/app/api/tasks/route.ts (modified, +4/-2)
  • frontend/app/api/transcribe/route.ts (added, +78/-0)
  • frontend/app/dashboard/pipeline/page.tsx (modified, +4/-5)
  • frontend/app/global-error.tsx (modified, +4/-4)
  • frontend/app/layout.tsx (modified, +23/-23)
  • frontend/components/app-header.tsx (modified, +16/-16)
  • frontend/components/chart-bar-donations.tsx (modified, +1/-1)
  • frontend/components/chat/chat-bar.tsx (modified, +4/-4)
  • frontend/components/chat/chat-overlay.tsx (modified, +1/-1)
  • frontend/components/donors/donor-filters.tsx (modified, +3/-3)
  • frontend/components/donors/donor-health-score.tsx (modified, +4/-4)
  • frontend/components/donors/save-report-button.tsx (modified, +2/-2)
  • frontend/components/feedback-dialog.tsx (modified, +2/-2)
  • frontend/components/onboarding-checklist.tsx (modified, +1/-1)
  • frontend/components/recent-activity.tsx (modified, +3/-3)
  • frontend/components/smart-actions.tsx (modified, +1/-1)
  • frontend/components/theme-toggle.tsx (modified, +4/-4)
  • frontend/components/views/chat-view.tsx (modified, +2/-2)
  • frontend/components/views/donations-view.tsx (modified, +1/-1)
  • frontend/components/views/donor-crm-view.tsx (modified, +3/-4)
  • frontend/components/views/donor-map-view.tsx (modified, +12/-23)
  • frontend/components/views/saved-reports-view.tsx (modified, +8/-9)
  • frontend/components/views/settings-view.tsx (modified, +4/-4)
  • frontend/lib/chat/pii-redactor.ts (added, +184/-0)
  • frontend/lib/chat/system-prompt.ts (modified, +6/-0)
  • frontend/lib/chat/tools.ts (modified, +27/-21)
  • frontend/lib/digest-ai.ts (modified, +8/-4)
  • frontend/lib/pii-redaction.ts (modified, +27/-0)
  • frontend/lib/recalc-donor-totals.ts (added, +29/-0)
  • frontend/proxy.ts (modified, +68/-3)
  • skills-lock.json (added, +155/-0)

Code Example

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

---

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

  return NextResponse.next();
}
RAW_BUFFERClick to expand / collapse

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. <img width="1906" height="559" alt="Image" src="https://github.com/user-attachments/assets/e086a0be-4188-4181-98ce-6b02c915767e" /> This screenshot shows quite a few (over 2000) {signal,abort} objects hanging around <img width="481" height="286" alt="Image" src="https://github.com/user-attachments/assets/728cf433-ef22-42d0-bd75-105f4920053d" />

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.

extent analysis

TL;DR

The most likely fix for the memory leak issue is to avoid using axios with an AbortSignal in middleware.ts or upgrade to a version where this issue is fixed, if available.

Guidance

  • The issue seems to be related to the use of axios with an AbortSignal in middleware.ts, which is causing a memory leak. Removing the AbortSignal or using fetch instead of axios resolves the issue.
  • To verify the issue, take a heap snapshot using kill -USR2 <next-server PID> and analyze it in Chrome DevTools to check for increasing memory usage.
  • As a temporary workaround, consider using fetch instead of axios in middleware.ts to avoid the memory leak.
  • If upgrading to a newer version of Next.js is an option, test the issue with the latest version to see if it has been resolved.

Example

// Temporary workaround: use fetch instead of axios in middleware.ts
export async function middleware(req: NextRequest) {
  const response = await fetch('https://<some-api>/', {
    signal: AbortSignal.timeout(1000),
  });
  return NextResponse.next();
}

Notes

The issue is specific to the use of axios with an AbortSignal in middleware.ts and does not occur when using fetch or when the AbortSignal is removed. The exact cause of the memory leak is not specified, but it is likely related to the interaction between axios and the AbortSignal.

Recommendation

Apply the workaround by using fetch instead of axios in middleware.ts, as this is a relatively simple change that can resolve the issue without requiring a significant migration effort.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

nextjs - ✅(Solved) Fix Memory leak with axios and AbortSignal in middleware in next >=15.4 [1 pull requests, 13 comments, 3 participants]