Memory Crashes and Build Failures

JavaScript Heap Out of Memory

The Error: FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory

This kills your builds when dealing with large codebases or problematic dependencies. This is a well-documented Node.js limitation that affects many JavaScript build tools. Here's what actually works:

Quick Fix:

NODE_OPTIONS=\"--max-old-space-size=8192\" npm run build

Permanent Fix (add to package.json):

{
  \"scripts\": {
    \"build\": \"NODE_OPTIONS='--max-old-space-size=8192' rollup -c\"
  }
}

Why This Happens:

Real Example: Had a client with a 15MB JSON config file being imported directly. Moving it to dynamic imports fixed the memory issue and cut build time from 12 minutes to 2.

Build Hangs Forever

Symptoms: Build starts, shows progress, then freezes at 90% with no error

Nuclear Option (works 90% of the time):

rm -rf node_modules package-lock.json
npm install
npm run build

Smarter Debugging:

## Run with verbose logging
rollup -c --verbose

## Check for circular dependencies
npm install --save-dev madge
madge --circular --format es6 src/

Circular dependency detection is crucial - the madge tool integrates well with most build systems and helps identify problematic import cycles.

Common Causes:

Pro Tip: If madge finds circular dependencies, draw them on paper. Seriously. Visual debugging works when your brain is fried at 3 AM.

Bundle Size Explosion

Problem: Bundle went from 300KB to 3MB overnight

Immediate Diagnosis:

npm install --save-dev rollup-plugin-visualizer

Add to rollup.config.js:

import { visualizer } from 'rollup-plugin-visualizer';

export default {
  plugins: [
    // ... other plugins
    visualizer({
      filename: 'dist/stats.html',
      open: true,
      gzipSize: true
    })
  ]
}

What You'll Usually Find:

Real War Story: Deployed on Friday, users complained site was slow on Monday. Bundle analyzer showed we were importing the entire @mui/icons-material package (2.1MB) because someone used a barrel import. One line change: import HomeIcon from '@mui/icons-material/Home'import HomeIcon from '@mui/icons-material/Home'. Bundle dropped to 200KB.

Plugin Hell Debugging

When Plugins Break Everything:

Plugin order matters more than Rollup admits. This order works for 95% of projects:

export default {
  plugins: [
    resolve({ preferBuiltins: false }),
    commonjs(),
    typescript(),
    babel(), 
    terser() // Always last
  ]
}

Plugin Conflict Debugging:

## Test each plugin individually
rollup -c rollup.config.minimal.js

Create rollup.config.minimal.js:

export default {
  input: 'src/index.js',
  output: { file: 'dist/test.js', format: 'esm' },
  plugins: [
    // Add plugins one by one until it breaks
  ]
}

Common Plugin Issues:

  • @rollup/plugin-commonjs gives up on weird packages (looking at you, react-dom)
  • @rollup/plugin-typescript ignores half your tsconfig.json settings
  • rollup-plugin-postcss breaks with CSS modules sometimes
  • @rollup/plugin-json crashes on huge JSON files

CI/CD Build Failures

GitHub Actions Running Out of Memory:

- name: Build with more memory
  run: NODE_OPTIONS=\"--max-old-space-size=4096\" npm run build

This is a common issue with GitHub Actions default runners that have memory limitations.

Vercel Deployment Failures:
Check your build output size. Vercel has a 250MB limit for the entire build directory.

## Check build size locally
du -sh dist/

See Vercel build troubleshooting for more deployment issues and build optimization strategies.

Docker Build Issues:

FROM node:18-alpine
## Increase memory for build step
ENV NODE_OPTIONS=\"--max-old-space-size=4096\"
RUN npm run build

Source Map Problems

Missing Source Maps in Production:

export default {
  output: {
    sourcemap: true,  // or 'inline' for debugging
    sourcemapExcludeSources: true  // Don't leak source code
  }
}

Source Maps Too Large:

export default {
  output: {
    sourcemap: process.env.NODE_ENV === 'development'
  }
}

Debugging with Source Maps:
When your production error points to line 1 column 47293, you need source maps to find the real location. Source maps are essential for production debugging and error tracking.

## Install source-map-cli for debugging
npm install -g source-map-cli
smc --source-map dist/bundle.js.map --line 1 --column 47293

For comprehensive error tracking, consider integrating Sentry source maps or Bugsnag source maps. This helps with production error monitoring.

Crisis Mode Questions

Q

Build worked locally, fails in CI. What's different?

A

First suspects: Node version differences, environment variables, or dependency cache.Quick fixes to try:bash# Lock Node version in CIecho "18.17.0" > .nvmrc# Clear CI cacherm -rf node_modules package-lock.jsonnpm ci --clean-install# Check environment differences env | grep NODEReal culprit 90% of the time: Your local has different dependencies than CI. Someone ran npm install instead of npm ci.

Q

Bundle loads but app crashes with "Cannot read property of undefined"

A

Diagnosis:

Tree-shaking removed something your code actually needs, or module loading order is wrong. Quick test:javascript// Temporarily disable tree-shakingexport default { treeshake: false, // ... rest of config}If that fixes it, you have a tree-shaking problem.

Look for:

  • Side effects in imported modules
  • Dynamic imports that Rollup can't analyze
  • Circular dependencies confusing module order
Q

Memory usage keeps growing on rebuilds

A

Symptom: First build uses 2GB RAM, second build uses 4GB, eventually crashesThis is a known Rollup issue with watch mode and certain plugins. Workarounds:bash# Use Vite for development insteadnpm install vite --save-dev# Or restart the build process periodicallyNODE_OPTIONS="--max-old-space-size=8192" npm run buildNuclear option: Switch to Vite for development, keep Rollup for production builds only.

Q

"Module not found" but the file exists

A

Usually means:

Path resolution is broken or you're mixing module systemsDebug steps:javascript// Add to rollup.config.js for debuggingimport { resolve } from '@rollup/plugin-node-resolve';export default { plugins: [ resolve({ preferBuiltins: false, browser: true, exportConditions: ['svelte'] // Adjust for your framework }) ]}Check your import paths:

  • ./Component vs ./Component.js
  • Case sensitivity on Linux vs Windows
  • Missing file extensions
Q

TypeScript types are wrong in production

A

Problem: Types work in dev but break after bundlingRoot cause: Rollup's TypeScript plugin generates different .d.ts files than tscSolutions:bash# Generate types separately with tscnpx tsc --emitDeclarationOnly --declaration# Then bundle JS with Rolluprollup -cOr use rollup-plugin-dts:javascriptimport dts from 'rollup-plugin-dts';export default [ // Normal JS bundle { /* ... */ }, // Separate types bundle { input: 'src/index.ts', output: { file: 'dist/index.d.ts', format: 'esm' }, plugins: [dts()] }];

Q

CSS not loading in production

A

Symptom: Styles work in dev, missing in production bundleQuick fix:javascriptimport postcss from 'rollup-plugin-postcss';export default { plugins: [ postcss({ extract: true, // Extract CSS to separate file minimize: true // Minify CSS }) ]}Common gotcha: CSS imports in TypeScript files need explicit .css extensions:typescript// This breaks in productionimport './styles'; // This worksimport './styles.css';

Advanced Debugging Techniques

Bundle Analysis Deep Dive

Beyond basic size analysis, you need to understand what's actually happening inside your bundle. Here's how to debug like a pro when shit hits the fan. These techniques are essential for production bundle optimization and performance troubleshooting.

Multiple Analysis Tools (use all of them):

## 1. Bundle visualizer (shows size relationships)
npm install --save-dev rollup-plugin-visualizer

## 2. Bundle analyzer (interactive treemap) 
npm install --save-dev webpack-bundle-analyzer

## 3. Bundle buddy (finds why modules were included)
npm install -g bundle-buddy

Each tool provides different insights: rollup-plugin-visualizer shows size relationships, webpack-bundle-analyzer creates interactive treemaps, and Bundle Buddy explains why each module was included through source map analysis.

Real Debugging Session:

// rollup.config.debug.js
import { visualizer } from 'rollup-plugin-visualizer';

export default {
  // ... normal config
  plugins: [
    // ... other plugins
    
    // Generate multiple analysis files
    visualizer({ filename: 'dist/bundle-stats.html' }),
    visualizer({
      filename: 'dist/bundle-treemap.html',
      template: 'treemap'
    }),
    visualizer({
      filename: 'dist/bundle-network.html',
      template: 'network'
    })
  ]
}

When Bundle Analysis Shows Weird Shit:

  • Duplicate modules: Same package bundled multiple times (usually version conflicts)
  • Massive chunks: One module importing everything else
  • Tiny chunks: Code splitting went too far
  • Mystery meat: Modules you don't recognize (probably transitive dependencies)

Memory Profiling Production Builds

Heap Snapshots During Build:

## Run with Node inspector
node --inspect-brk node_modules/.bin/rollup -c

## Open Chrome, go to chrome://inspect
## Take heap snapshots at different build stages

Memory Leak Detection:

// Add to rollup.config.js for memory monitoring
const memwatch = require('memwatch-next');

memwatch.on('leak', (info) => {
  console.error('Memory leak detected:', info);
});

export default {
  // ... your config
}

Memory profiling is essential for Node.js performance debugging and understanding V8 heap behavior. Use Chrome DevTools memory tab for detailed analysis.

Real Memory Problem I Solved:
Client's build was using 12GB RAM and failing. Heap analysis showed the TypeScript plugin was holding onto AST nodes from every file. Solution: Updated to latest plugin version and split the build into smaller chunks. Memory usage dropped to 2GB.

Performance Profiling

Build Time Analysis:

## Time each plugin
time rollup -c --silent

## More detailed timing
ROLLUP_WATCH_INCLUDE_TIME=1 rollup -c --watch

Plugin Performance Debugging:

// Wrap plugins to measure timing
function timePlugin(plugin, name) {
  const start = Date.now();
  return {
    ...plugin,
    buildEnd() {
      console.log(`${name}: ${Date.now() - start}ms`);
      if (plugin.buildEnd) plugin.buildEnd.call(this);
    }
  };
}

export default {
  plugins: [
    timePlugin(resolve(), 'resolve'),
    timePlugin(commonjs(), 'commonjs'),
    timePlugin(typescript(), 'typescript')
  ]
}

Dependency Hell Debugging

Finding Version Conflicts:

## Show duplicate packages
npm ls --depth=0 | grep -E "├─|└─" | sort

## Check for version mismatches
npm install --save-dev npm-check-updates
npx ncu

## Show why a package was installed
npm explain package-name

Version conflicts are a major source of build failures. Understanding npm dependency resolution and package hoisting helps debug these issues. Use npm-check-updates to identify outdated packages.

Forcing Resolutions (last resort):

{
  "overrides": {
    "problematic-package": "2.1.0"
  }
}

Real Example:
Had three different versions of @babel/core in the dependency tree. Build would randomly fail depending on which version got loaded first. Fixed with package-lock.json cleanup and explicit overrides.

Production Runtime Errors

Source Map Debugging in Chrome:

  1. Deploy source maps to a private URL
  2. Add source map comment to production bundle:
    export default {
      output: {
        sourcemap: true,
        sourcemapFile: 'https://private-cdn.com/sourcemaps/[name].js.map'
      }
    }
    

Error Tracking Integration:

// rollup.config.production.js
export default {
  plugins: [
    // Add build info to bundle for error tracking
    replace({
      '__BUILD_DATE__': JSON.stringify(new Date().toISOString()),
      '__GIT_COMMIT__': JSON.stringify(process.env.GIT_COMMIT || 'unknown')
    })
  ]
}

CI/CD Debugging

GitHub Actions Debug Logs:

- name: Debug Rollup build
  run: |
    echo "Node version: $(node --version)"
    echo "Memory available: $(free -h)"
    echo "Disk space: $(df -h)"
    NODE_OPTIONS="--max-old-space-size=4096" npm run build
  env:
    DEBUG: rollup:*

Docker Build Optimization:

FROM node:18-alpine
WORKDIR /app

## Copy dependency files first (better caching)
COPY package*.json ./
RUN npm ci --only=production

## Copy source and build
COPY . .
ENV NODE_OPTIONS="--max-old-space-size=4096"
RUN npm run build

## Debug: Check what we built
RUN ls -la dist/ && du -sh dist/*

Vercel Build Debugging:

{
  "functions": {
    "app/**": {
      "maxDuration": 300
    }
  },
  "build": {
    "env": {
      "NODE_OPTIONS": "--max-old-space-size=4096"
    }
  }
}

Emergency Rollback Procedures

When Production Deploy Breaks:

  1. Immediate rollback (if using CDN):

    # Revert to previous bundle
    aws s3 cp s3://cdn-bucket/bundle-v1.2.3.js s3://cdn-bucket/bundle.js
    
  2. Quick hotfix (minimal working bundle):

    // rollup.config.emergency.js
    export default {
      input: 'src/index.js',
      output: { file: 'dist/bundle.js', format: 'iife' },
      plugins: [
        // Minimum plugins only
        resolve(),
        commonjs()
      ],
      treeshake: false  // Disable optimizations
    }
    
  3. Diagnostic bundle (helps debug in production):

    export default {
      output: {
        format: 'iife',
        sourcemap: 'inline',  // Inline for easier debugging
        compact: false        // Don't minify
      }
    }
    

Emergency Debugging Questions

Q

Bundle works in Chrome but breaks in Safari/Firefox

A

Most likely cause: You're using modern JavaScript features that need polyfills

Quick fix:

import { babel } from '@rollup/plugin-babel';

export default {
  plugins: [
    babel({
      presets: [
        ['@babel/preset-env', {
          targets: { browsers: ['> 1%', 'last 2 versions'] }
        }]
      ]
    })
  ]
}

Common culprits: Optional chaining (?.), nullish coalescing (??), dynamic imports in older browsers

Q

Build is slow as shit, how do I speed it up?

A

1. Enable parallel processing:

export default {
  plugins: [
    typescript({
      transpileOnly: true  // Skip type checking in build
    }),
    terser({
      numWorkers: require('os').cpus().length  // Use all CPU cores
    })
  ]
}

2. Split the build:

## Type check separately
npx tsc --noEmit &

## Build in parallel
rollup -c &
wait

3. Cache everything:

export default {
  cache: true,  // Enable Rollup's cache
  plugins: [
    typescript({
      incremental: true  // TypeScript incremental builds
    })
  ]
}

Nuclear option: Use SWC instead of Babel. 20x faster, works with most configs.

Q

Deploy worked but users getting 404s for chunks

A

Problem: Code splitting created new chunk names, old chunks got deleted

Solutions:

export default {
  output: {
    // Stable chunk names
    chunkFileNames: '[name]-[hash].js',

    // Better cache control
    manualChunks: {
      vendor: ['react', 'react-dom'],
      utils: ['lodash-es', 'date-fns']
    }
  }
}

CDN cache busting:

## Invalidate CDN cache after deploy
aws cloudfront create-invalidation --distribution-id ABCD --paths "/*"
Q

Plugin says "Cannot find module" but I just installed it

A

Restart your dev server. I'm serious. Plugin resolution caches are aggressive.

If that doesn't work:

## Nuclear option for plugin issues
rm -rf node_modules package-lock.json .rollup.cache
npm install

Check plugin compatibility:

npm list @rollup/plugin-commonjs

Some plugin versions just don't work together. Pin working versions in package.json.

Q

Source maps point to wrong lines

A

Cause: Minification or plugin transformations scrambling the mapping

Fix: Generate source maps at each step:

export default {
  plugins: [
    typescript({ sourceMap: true }),
    babel({ sourceMaps: true }),
    terser({ sourceMap: true })
  ],
  output: {
    sourcemap: true
  }
}

Debug specific files:

## Check source map validity
npm install -g source-map-cli
smc --source-map dist/bundle.js.map --generated-line 1 --generated-column 1000
Q

App loads but features are missing in production

A

Tree-shaking killed something important. This happens with:

  • CSS imports without explicit .css extension
  • Dynamic imports that Rollup can't analyze
  • Side effects in imported modules

Quick test:

export default {
  treeshake: {
    moduleSideEffects: true  // Preserve all side effects
  }
}

Proper fix: Mark files with side effects in package.json:

{
  "sideEffects": ["**/*.css", "src/polyfills.js"]
}
Q

Getting "ReferenceError: exports is not defined"

A

Problem: Mixing CommonJS and ES modules incorrectly

Fix: Configure the CommonJS plugin properly:

import commonjs from '@rollup/plugin-commonjs';

export default {
  plugins: [
    commonjs({
      include: 'node_modules/**',  // Only convert node_modules
      exclude: ['node_modules/some-es-module/**']
    })
  ]
}

Alternative: Convert everything to ES modules and avoid the plugin entirely.

Emergency Resources When Everything's Broken