Next.js Build Memory: Solving JavaScript Heap Out of Memory
Why Next.js builds run out of memory and how to fix JavaScript heap out of memory errors with practical solutions
Next.js Build Memory: Solving JavaScript Heap Out of Memory
Introduction
Few errors are as frustrating as a build that crashes with JavaScript heap out of memory. You watch the build progress bar, it gets to 80%, and then the entire process dies. The error message is not helpful because it does not tell you what is consuming the memory or how to fix it.
I first encountered this issue on a project with 400+ pages and hundreds of thousands of lines of markdown content. The default Node.js heap allocation was nowhere near sufficient. This post walks through every strategy I used to resolve it.
Environment
- OS: Windows 11
- Node.js: v20.10.0
- Next.js: 14.0.4
- Available RAM: 32GB
- Project Size: ~500 pages, 200K+ lines of content
Problem
Running next build produced:
<--- Last few GCs --->
[4824:000001E8A3C00000] 1234567 ms: Scavenge 4096.0 (4500.0) -> 4096.0 (4500.0) MB, 10.0 / 0.0 ms (average = 10.0 ms, current = 10.0 ms) allocation failure
[4824:000001E8A3C00000] 1234600 ms: Mark-sweep 8192.0 (8500.0) -> 4096.0 (4500.0) MB, 200.0 / 0.0 ms (average = 50.0 ms, current = 200.0 ms) allocation failure
<--- JS stacktrace --->
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
npm ERR! code ELIFECYCLE
npm ERR! errno 134The build consumed over 8GB of RAM before crashing. Increasing the heap limit to 16GB delayed but did not prevent the crash.
Analysis
The memory consumption came from three sources:
1. Large data payloads in getStaticProps. Fetching thousands of records at build time and storing them in memory for page generation.
2. Image optimization. Processing hundreds of images simultaneously during the build creates massive memory pressure.
3. Webpack compilation. The default webpack configuration is not optimized for very large codebases.
4. Content parsing. Parsing markdown or MDX files with syntax highlighting plugins is extremely memory-intensive.
Solution
Fix 1: Increase the Node.js heap limit
// package.json
{
"scripts": {
"build": "cross-env NODE_OPTIONS='--max-old-space-size=8192' next build"
}
}# Or run directly
set NODE_OPTIONS=--max-old-space-size=8192
next buildFix 2: Optimize data fetching for static generation
// app/blog/[slug]/page.tsx
// Instead of fetching all posts at once, use on-demand generation
export const dynamicParams = true
export async function generateStaticParams() {
// Only generate the most important pages at build time
const posts = await getPopularPosts(100) // Limit to top 100
return posts.map((post) => ({ slug: post.slug }))
}
async function getPost(slug: string) {
const res = await fetch(`https://api.example.com/posts/${slug}`, {
next: { revalidate: 3600 },
})
return res.json()
}Fix 3: Batch image optimization
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
// Limit concurrent image optimizations
minimumCacheTTL: 60 * 60 * 24 * 30, // 30 days
deviceSizes: [640, 750, 828, 1080],
imageSizes: [16, 32, 48, 64, 96, 128, 256],
},
// Reduce webpack memory usage
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
}
}
return config
},
}
module.exports = nextConfigFix 4: Use dynamic imports for heavy components
// Instead of importing everything at the top
import { HeavyChart } from '@/components/HeavyChart'
// Use dynamic import with loading state
import dynamic from 'next/dynamic'
const HeavyChart = dynamic(() => import('@/components/HeavyChart'), {
loading: () => Loading chart...,
ssr: false, // Skip server rendering for heavy components
})Fix 5: Monitor and profile the build
# Enable Node.js heap snapshots
NODE_OPTIONS="--max-old-space-size=8192 --heapsnapshot-signal=SIGUSR2" next build
# Profile webpack compilation
NEXT_PROFILE_BUILD=1 next build// scripts/build-monitor.js
const { execSync } = require('child_process')
function monitorBuild() {
const startMem = process.memoryUsage()
console.log('Starting build with memory:', startMem.heapUsed / 1024 / 1024, 'MB')
try {
execSync('next build', {
stdio: 'inherit',
env: {
...process.env,
NODE_OPTIONS: '--max-old-space-size=8192',
},
})
} catch (error) {
console.error('Build failed:', error.message)
process.exit(1)
}
}
monitorBuild()Lessons Learned
8GB heap limit is a band-aid, not a solution. If your build needs 8GB of RAM, something is structurally wrong with how data is being processed.
Paginate data fetching at build time. Do not fetch all records into memory. Use cursor-based pagination and process data in streams.
Use
generateStaticParamswith limits. Only pre-render the most important pages. Let ISR handle the rest.Profile before optimizing. Use
--inspectflag to attach Chrome DevTools and identify actual memory consumers:
NODE_OPTIONS="--max-old-space-size=8192 --inspect" next buildThen open chrome://inspect in Chrome to connect to the build process.
- Consider splitting the build. For very large sites, build different sections separately using Next.js's output: 'standalone' mode.
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.