# Next.js build slow or out of memory in GitHub Actions: cache, memory, runner size

> A Next.js build in GitHub Actions is slow when .next/cache isn't carried to the next run, and runs out of memory on a private repository's 8 GB ubuntu-latest once webpack, type checking and source maps add up. What to cache, which Next.js settings cut memory, and the runner size: 16 GB for $79 a month or 32 GB for $149 on Skipbox, unlimited minutes.

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.

- `.next/cache` carried over: the build reuses its last compile instead of starting cold
- 16 GB for $79 a month or 32 GB for $149, against 8 GB on a private `ubuntu-latest`
- Package caches stay on your machine, and cache restores read its disk instead of downloading

**GitHub Actions at a flat price, unlimited minutes** Skipbox runs your GitHub Actions jobs on your own dedicated machine: $39 a month, however many minutes you run. Docker layers and dependency caches stay between jobs, so the next job starts right away.

A 4 vCPU runner, 20,000 minutes a month: GitHub $222 → Skipbox $39. You save $183 every month.

Moving takes one line: `runs-on: skipbox`.

[Start my 7-day free trial](https://app.skipbox.dev/sign-in?ref=g-nextjs-build-slow-out-of-memory-github-actions-en)

## Slow: .next/cache starts empty

From Next.js's [CI build caching guide](https://nextjs.org/docs/app/guides/ci-build-caching): 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:

```yaml
- 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](https://turborepo.dev/docs/guides/ci-vendors/github-actions) 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](https://nextjs.org/docs/app/guides/memory-usage):

- **`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: false` and `experimental.serverSourceMaps: false`. If the build runs out during "Generating static pages", `enablePrerenderSourceMaps: false` too.
- **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: true` skips 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](https://docs.github.com/en/actions/concepts/runners/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=12288` on M.
- **Package caches stay on the disk:** `~/.npm`, the pnpm store, each tool's `~/.cache`, and the Node versions `setup-node` installed. `npm ci` reads them instead of the registry.
- **`actions/cache` restores read the machine.** `.next/cache` is 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

```yaml
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`.

1. Sign in to Skipbox with GitHub.
2. Install the Skipbox GitHub App and pick the repositories.
3. Change `runs-on` and 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](https://docs.skipbox.dev/isolation).

## 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 2026-11-13 · https://skipbox.dev/guides/nextjs-build-slow-out-of-memory-github-actions
