Next.js build slow or out of memory in GitHub Actions: cache, memory, runner size
A Next.js build in GitHub Actions is slow for one reason and runs out of memory for another. It is slow when .next/cache isn't carried to the next run: Next.js reuses that cache between builds, a GitHub-hosted job starts on a new VM, and without a cache step every build is cold (the log says No Cache Detected). It runs out of memory because a private repository's ubuntu-latest has 8 GB, and a large app's webpack compile, type check and source maps can pass that. Fix the first with actions/cache on .next/cache. For the second, Next.js's memory settings come first (experimental.webpackMemoryOptimizations, no production source maps); then a machine with more memory: 16 GB or 32 GB.
Skipbox gives the build that memory at a flat price: 16 GB and 8 vCPU on runs-on: skipbox-8x16 for $79 a month, 32 GB and 16 vCPU on skipbox-16x32 for $149. On your own machine the package caches stay on the disk and what actions/cache saves is read locally, with unlimited minutes.
Slow: .next/cache starts empty
From Next.js's CI build caching guide: Next.js "saves a cache to .next/cache that is shared between builds", and a CI that doesn't persist it between builds shows a No Cache Detected error. Its GitHub Actions step:
- uses: actions/cache@v4
with:
path: |
~/.npm
${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
The key changes with every source change, so each run saves a new entry, inside GitHub's 10 GB per repository. In a Turborepo, exclude .next/cache from the task's outputs ("!.next/cache/**"), as Turborepo's GitHub Actions guide does.
Out of memory: two different errors
| In the log | What ran out | What to do |
|---|---|---|
JavaScript heap out of memory | Node's own heap limit; the machine still had memory | Raise it: NODE_OPTIONS=--max-old-space-size=6144, below the machine's memory |
Process completed with exit code 137 | The machine's memory: Linux killed the process | Use less memory, or a machine with more |
Raising the heap past the machine's memory turns the first error into the second.
Use less memory: Next.js's own settings
From Next.js's memory usage guide:
experimental.webpackMemoryOptimizations: true(v15.0.0 and later) lowers webpack's peak memory, at a slightly longer compile.- The webpack build worker runs compilation in a separate Node worker, which lowers memory. It is on by default since v14.1.0 unless you have a custom webpack config; with one, set
experimental.webpackBuildWorker: true. - Source maps off:
productionBrowserSourceMaps: falseandexperimental.serverSourceMaps: false. If the build runs out during "Generating static pages",enablePrerenderSourceMaps: falsetoo. - Measure:
next build --experimental-debug-memory-usage(v14.2.0 and later) prints heap use through the build, and takes heap snapshots near the limit. - Type checking:
typescript.ignoreBuildErrors: trueskips the type check and its memory, and lets type errors into the build. Next.js warns against it unless another step type-checks.
Use a machine with more memory
| Runner | CPU and memory | Who can use it |
|---|---|---|
GitHub ubuntu-latest, private repository | 2 CPUs, 8 GB | Everyone |
GitHub ubuntu-latest, public repository | 4 CPUs, 16 GB | Public repositories |
| GitHub larger runner, 4 or 8 cores | 16 GB or 32 GB | GitHub Team and Enterprise Cloud organizations, per minute (larger runners) |
Skipbox skipbox-8x16 | 8 vCPU, 16 GB | Organizations and personal accounts, $79 a month |
Skipbox skipbox-16x32 | 16 vCPU, 32 GB | Organizations and personal accounts, $149 a month |
At 20,000 minutes a month, GitHub's 8-core runner is $422 and Skipbox M is $79; at 16 cores, $822 against $149 for L. GitHub's figures are after GitHub Team's included usage.
What Skipbox does for a Next.js build
Each plan comes with your account's own machine, kept between jobs:
- The memory the build needs, by label: 16 GB on M, 32 GB on L. Set the heap to match, such as
--max-old-space-size=12288on M. - Package caches stay on the disk:
~/.npm, the pnpm store, each tool's~/.cache, and the Node versionssetup-nodeinstalled.npm cireads them instead of the registry. actions/cacherestores read the machine..next/cacheis an untracked folder, and the checkout's untracked files are cleaned after each job, so keep the cache step. What it saves stays next to the machine, with more room than GitHub's 10 GB per repository, and the restore is a local read.- More cores at a flat price. The compile and the static page generation use them, and minutes are unlimited.
| Label | Machine | Plan |
|---|---|---|
skipbox | 4 vCPU · 16 GB | S, $39 a month |
skipbox-8x16 | 8 vCPU · 16 GB | M, $79 a month |
skipbox-16x32 | 16 vCPU · 32 GB | L, $149 a month |
Moving is one line
jobs:
build:
runs-on: skipbox-8x16 # was ubuntu-latest
env:
NODE_OPTIONS: --max-old-space-size=12288
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- uses: actions/cache@v4
with:
path: ${{ github.workspace }}/.next/cache
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }}
restore-keys: |
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json') }}-
- run: npm ci
- run: npx next build
skipbox-8x16 runs on an M plan; the trial runs runs-on: skipbox.
- Sign in to Skipbox with GitHub.
- Install the Skipbox GitHub App and pick the repositories.
- Change
runs-onand push.
Security
- The machine is your account's alone. No other company's jobs run on it.
- After every job, the runner's credentials, processes and containers are wiped, and so are the git credentials and untracked files in the kept checkout. Clean every job gives each job a fresh machine instead.
- Code from people without write access (such as pull requests from forks) runs on a fresh VM that reads your caches but can't change them.
The details are in how jobs are isolated.
Try it on the Next.js build
The trial is free for 7 days or 3,000 job-minutes, whichever comes first, with no card. It runs runs-on: skipbox, with 16 GB, 2× the memory of a private repository's ubuntu-latest: try the cache and the memory there, and move a build that needs more to plan L (skipbox-16x32, 32 GB).
FAQ
Why is next build slow in GitHub Actions?
Usually because .next/cache starts empty. Next.js saves a build cache to .next/cache and reuses it on the next build, but a GitHub-hosted job is a new VM, so without a step that carries .next/cache over, every build is a cold one and the log warns No Cache Detected. Add actions/cache for .next/cache keyed on the lockfile and your source files. The rest of the time is the install and the compile on 2 CPUs.
What runner size should a Next.js build use in GitHub Actions?
Enough memory for the build's peak. A private repository's ubuntu-latest has 2 CPUs and 8 GB, and a large Next.js app's webpack build, type check and source maps can pass that. GitHub's larger runners have 16 GB at 4 cores and 32 GB at 8, only on GitHub Team or Enterprise Cloud, per minute. Skipbox gives 16 GB with 8 vCPU on runs-on: skipbox-8x16 for $79 a month, and 32 GB with 16 vCPU on skipbox-16x32 for $149.
How do I fix JavaScript heap out of memory in a Next.js build on GitHub Actions?
That error is Node's own heap limit, not the machine's memory. Raise it with NODE_OPTIONS=--max-old-space-size=<MiB>, set below the machine's memory. If the job then ends with exit code 137 instead, the machine itself ran out: cut the build's memory (experimental.webpackMemoryOptimizations, no production source maps) or run on a machine with more memory.
How do I reduce Next.js build memory usage?
From Next.js's memory guide: try experimental.webpackMemoryOptimizations (v15 and later), keep the webpack build worker on (the default without a custom webpack config since v14.1.0), turn off productionBrowserSourceMaps and experimental.serverSourceMaps, and measure with next build --experimental-debug-memory-usage. Skipping type checking with typescript.ignoreBuildErrors saves memory but lets type errors through, so only do it when another CI job type-checks.
Does Skipbox keep .next/cache between jobs?
Keep your actions/cache step for it: .next/cache is an untracked folder, and the checkout's untracked files are cleaned after each job. What changes is the restore: on a Skipbox own machine, what actions/cache saves stays next to the machine, so the restore is a local read instead of a download. npm's or pnpm's cache, the Node versions setup-node installed and Docker's layers stay on the disk itself.
How much does it cost to try?
Nothing. The trial is 7 days or 3,000 job-minutes, whichever comes first, with no card, on 4 vCPU and 16 GB: 2× the 8 GB of ubuntu-latest on a private repository, and the same as GitHub's 4-core larger runner. Try the caching and the memory there; for a build that needs more, pick plan L and change the label to skipbox-16x32 (32 GB).
Updated November 13, 2026