All Articles

Resolving NestJS Heap Memory Errors with SWC

When working on a NestJS project recently, I encountered repeated crashes due to JavaScript heap memory errors in development mode. The specific error message was:

FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory

In this blog, I outline the root cause, the approaches I tried, and the final robust solution I arrived at using SWC with NestJS.

Understanding the Problem

My development workflow involved running my NestJS application using:

"start:dev": "dotenv -e .env.development -- nest start --watch"

This uses TypeScript’s incremental compilation under the hood (tsc --watch). With repeated reloads (file saves), the memory used by the compiler process ballooned until it hit Node.js’s heap limit (~2GB by default), causing a crash.

Initially, I suspected a memory leak within my application. But investigating deeper revealed the issue stemmed from TypeScript’s watch mode, particularly known to occur with certain TypeScript versions (especially in the 4.9.x and 5.4.x series). My project used TypeScript 4.9.5, known for this exact memory regression.

Early Attempts at Resolution

Initially, I attempted to mitigate this by downgrading TypeScript to 4.7.4. However, the problem persisted. Increasing the heap size temporarily (via Node’s NODE_OPTIONS=--max-old-space-size=4096) was a stop-gap, but not a permanent solution:

"start:dev": "cross-env NODE_OPTIONS=--max-old-space-size=4096 dotenv -e .env.development -- nest start --watch"

This gave some breathing room but wasn’t an elegant or permanent fix.

Switching to SWC as a Robust Solution

To permanently resolve the issue, I switched the NestJS build tooling to SWC—a fast Rust-based compiler. NestJS provides built-in support for SWC since version 10. This approach avoids memory leaks caused by TypeScript’s watch mode entirely.

Step 1: Upgrading Dependencies

I upgraded the NestJS CLI and related tools from version 9.x.x to 11.x.x:

npm install -D @nestjs/cli@^11.0.7 @nestjs/schematics@^11.0.5

I also added SWC and necessary helpers explicitly, which were previously missing:

npm install -D @swc/cli@^0.7.3 @swc/core@^1.11.24 @swc/helpers@^0.5.17

Step 2: Updating nest-cli.json (Optional)

Initially, I configured SWC as the default compiler by updating nest-cli.json:

{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "deleteOutDir": true,
    "builder": "swc"
  }
}

However, I later realized explicitly specifying SWC in scripts is sufficient, and you can optionally remove the builder property from nest-cli.json:

{
  "$schema": "https://json.schemastore.org/nest-cli",
  "collection": "@nestjs/schematics",
  "sourceRoot": "src",
  "compilerOptions": {
    "deleteOutDir": true
  }
}

Step 3: Creating an SWC-specific Script

I added an explicit script in package.json to start the development server using SWC:

"scripts": {
  "start:dev:swc": "dotenv -e .env.development -- nest start --watch --builder swc --verbose"
}

Addressing Import Issues

After switching to SWC, another issue arose due to the handling of CommonJS imports. Previously working imports like:

import * as cookieParser from 'cookie-parser';
import * as session from 'express-session';
import * as passport from 'passport';

began to fail because SWC doesn’t auto-unwrap CommonJS default exports.

To resolve this, I enabled esModuleInterop and allowSyntheticDefaultImports in tsconfig.json:

{
  "compilerOptions": {
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true
  }
}

Then updated imports to default style:

import cookieParser from 'cookie-parser';
import session from 'express-session';
import passport from 'passport';
import express from 'express';

This completely resolved runtime import errors.

Important Note: SWC and Type Checking

Unlike the TypeScript compiler (tsc), SWC does not perform type-checking during compilation—it only transforms TypeScript to JavaScript. Thus, type errors (e.g., missing properties, type mismatches) are not caught at compile-time by SWC.

To maintain type safety, I added a separate type-checking script to periodically verify types:

"scripts": {
  "typecheck": "tsc --noEmit"
}

Run this periodically or in your CI/CD pipeline:

npm run typecheck

Final Result

With these changes:

  • Upgrading NestJS CLI and dependencies
  • Configuring SWC explicitly in scripts
  • Adjusting import styles
  • Adding explicit type-checking scripts

The heap memory issue was completely resolved, compilation became significantly faster, and the developer experience dramatically improved.

SWC-based compilation, combined with explicit type-checking, is now my default recommendation for NestJS development mode to avoid TypeScript-related memory issues and enhance productivity.

Published May 3, 2025

I am a software developer and more recently a generative AI consultant. I am passionate about connecting applications to generative AI.