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.
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.
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.
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.
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
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
}
}
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"
}
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.
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
With these changes:
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.