A Vercel build that finished in 8 minutes last week suddenly runs for 45 minutes and gets killed with Build step did not complete within the maximum of 45 minutes. Your local next build or astro build finishes in 4 minutes. The 45-minute wall is identical on Hobby, Pro, and Enterprise (as of June 2026), so upgrading your plan tier does nothing — but on a paid plan you can put the build on a faster machine, which is often the quickest real fix.
Fastest path: open the failing build log and look at the last line before the kill. If it stalled inside Generating static pages (...), you have a page-count explosion (cause 2). If it stalled inside a postbuild script with no progress lines, a post-build network call hung (cause 3). If page generation got progressively slower batch-by-batch, you are running out of heap (cause 4). If it failed on the very first cold deploy after a week of inactivity, your build cache was evicted (cause 1). Match the symptom, then jump to the matching fix.
Which bucket are you in?
| Symptom in the build log | Most likely cause | Jump to |
|---|---|---|
No build cache found at the top; only slow on the first deploy after days idle | Cache evicted, full cold install | Step 1 |
Generating static pages (47832/250000) — way more pages than usual | Unbounded getStaticPaths / content set | Step 2, Step 6 |
A postbuild script stops printing and never returns | Hung network call in a post-build hook | Step 3 |
Each batch of pages slower than the last; heap out of memory | Memory pressure / GC thrash | Step 7 |
Downloading Chromium... / Downloading libvips... dominates install | Heavy postinstall binary download | Step 5 |
tsc prints nothing for 5+ minutes | Whole-graph type-check on a monorepo | Step 4 |
Optimizing bundle... / Minifying... stuck for minutes | Oversized client bundle | Step 7 |
Common causes
Ordered by what we see most often.
1. Build cache evicted, full cold install
Vercel’s build cache persists node_modules/** plus your framework’s cache directory (.next/cache, .astro/, etc.) across deploys. As of June 2026 each cache is capped at 1 GB per project and retained for one month. The cache key is derived from your account/team, project, Framework Preset, Root Directory, Node.js version, package manager, and Git branch — change any one of those (a Node major bump, a different package manager, a new branch with no production cache to fall back on) and the key changes, so the next build is a cold install + cold framework cache and can take 4-6x longer.
How to spot it: A fast build log starts by restoring the previous cache; the slow build shows No build cache found or restores nothing. New Git branches with no parent production cache are a classic trigger.
2. getStaticPaths / content collection returns an unbounded set
A typo like paths: posts.flatMap(p => tags.map(t => p)) instead of mapping per tag can suddenly produce 10x-100x the expected number of pages. Each page costs a few hundred ms; 50,000 pages can eat the entire 45 minutes alone. Vercel’s own guidance: once you cross roughly 100,000 output files, build times climb sharply.
How to spot it: Build log shows Generating static pages (47832/250000) instead of a normal page count. Compare with the last green build’s page count.
3. A post-build script hangs on a network fetch
Sitemap, RSS, OG-image generation, or search-index sync that calls an external API can hang for the full HTTP timeout per call. Multiply by hundreds of items and you blow the budget.
How to spot it: The next build / astro build portion finishes in 5 minutes, but node scripts/build-sitemap.mjs (or similar) hangs in the log with no progress lines.
4. Memory pressure causing GC thrash near build end
The build container has a fixed 8192 MB of memory on Standard machines (the default on Hobby and Pro). If the build process exhausts the heap and drops into V8’s slow GC mode, the last 20% of pages can take 10x the time of the first 80%; eventually it gets cancelled for exceeding memory, or hits the wall.
How to spot it: Page generation slows visibly per-batch in the log (e.g. first 1000 pages in 2 min, next 1000 in 8 min), or you see FATAL ERROR: ... heap out of memory, or the build is cancelled with a memory-limit message.
5. Large dependency install (puppeteer, sharp-libvips, Playwright)
A postinstall that downloads a 300 MB Chromium binary every cold build can add several minutes by itself. Combined with a cold cache it tips the build over the limit.
How to spot it: Downloading Chromium ... or Downloading libvips ... in the install log, taking longer than the rest of the install combined.
6. Type-checking on a monorepo doing a whole-graph rebuild
tsc --noEmit across 200 packages without project references / incremental mode walks every file every time. On a cold cache this can be 10-20 minutes alone.
How to spot it: Build log has a tsc invocation that prints nothing for 5+ minutes before continuing.
7. Sourcemap / minification on an oversized bundle
If a single client bundle has ballooned past ~50 MB (often from a dynamic import that accidentally pulled in aws-sdk v2 or mongodb), Terser / SWC minification can take far longer than usual.
How to spot it: Build log shows Optimizing bundle... or Minifying... stuck for many minutes. Compare bundle size with the last green build.
Before you start
- Capture the full build log from the failing build: Project → Deployments → the errored deployment → expand the Building accordion.
- Check Observability → Build Diagnostics in the dashboard sidebar — it charts your build durations over time so you can see exactly when the regression started.
- Note the exact failure mode: hard 45-minute kill, a cancellation for exceeding memory/disk, or a hang with no final log line?
- Compare the last green build’s duration and page count to the failing build’s last logged page count.
- Confirm
vercel buildruns locally to completion (with--prod) so you can A/B against CI.
Information to collect
- Build start/end timestamps and the last successful log line.
next.config.js/astro.config.mjsand whether ISR / SSG /output: staticis configured.- The page-generation count from a healthy build vs. the failing one.
package.jsonscripts.buildand anypostbuild/postinstallhook.- Set
VERCEL_BUILD_SYSTEM_REPORT=1as an env var to force a per-build system report. It breaks down the on-disk size of your source,node_modules, and output, flags any file over 100 MB, and surfaces hidden out-of-memory events — invaluable for causes 4, 5, and 7. - Whether the project uses Turbo / Nx / Lerna and what the build graph looks like.
Step-by-step fix
Ordered by ROI.
Step 1: Re-trigger with cache disabled to isolate cold vs. warm
In the dashboard: Deployments → the three dots on the deployment → Redeploy → in the dialog, leave Use existing Build Cache unchecked. (Equivalents: vercel --force from the CLI, or set the env var VERCEL_FORCE_NO_BUILD_CACHE=1.) If the cold rebuild also runs 45 minutes, the problem is in the build itself, not cache eviction. If only the first cold build is slow and warm builds are fine, cache eviction is your cause — go to the prevention notes below.
Step 2: Print the page count early in the build
Add a quick assertion in a pre-build script so a runaway source set fails fast instead of burning 45 minutes:
// scripts/check-page-count.mjs
import { glob } from "glob";
const files = await glob("src/content/**/*.{md,mdx}");
console.log(`[precheck] content files: ${files.length}`);
if (files.length > 10000) {
console.error("[precheck] page count over threshold");
process.exit(1);
}
Wire it as prebuild in package.json.
Step 3: Gate post-build scripts with a hard timeout
Wrap any networked post-build step with the timeout command so a hung fetch can’t run out the clock:
# package.json scripts.postbuild
"postbuild": "timeout 300 node scripts/build-sitemap.mjs || echo 'sitemap step skipped'"
timeout 300 kills the step at 5 minutes; the || lets the build proceed and the sitemap regenerates next deploy. A stale sitemap beats a killed deploy. You can apply the same trick to the whole build — timeout 40m next build exits with status 124 and logs Error: Command "timeout 40m next build" exited with 124, so you get a clean fail with logs intact instead of an opaque platform kill at 45 minutes.
Step 4: Split a monorepo into Turborepo tasks with caching
If this is a monorepo, cache per-package outputs so unchanged packages skip rebuilding, and cap concurrency so you don’t OOM:
// turbo.json
{
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"],
"cache": true
}
}
}
Then run turbo run build --concurrency=4 instead of npm run build at the root. Cache hits across packages save 60-80% on subsequent runs. For a whole-graph tsc, enable TypeScript project references and tsc -b --incremental so only changed packages re-typecheck — or move type-checking out of the build entirely (see Step 8).
Step 5: Move heavy binaries out of the build
For Chromium / Playwright: don’t download at install time on Vercel. Use @sparticuz/chromium packaged inside the function only, or run screenshotting in a separate worker (Render, Fly, an AWS Lambda layer) and have the build fetch URLs instead.
# package.json
"postinstall": "echo 'skipping chromium download in build'",
Set PUPPETEER_SKIP_DOWNLOAD=true in Vercel env vars (use PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 for Playwright).
Step 6: Switch bulk SSG pages to ISR or on-demand generation
If getStaticPaths returns 50k+ pages and most are rarely hit, pre-render only the hot ones and generate the long tail on first request:
// pages/posts/[slug].tsx
export async function getStaticPaths() {
const topPosts = await fetchTopPosts(500); // pre-build only 500
return {
paths: topPosts.map(p => ({ params: { slug: p.slug } })),
fallback: "blocking", // generate the rest on first request
};
}
Build time drops linearly with the pre-generated set. See Next.js ISR revalidation stuck for the runtime behavior.
Step 7: Raise the Node heap (within the container ceiling) and bisect
The Standard build container has a fixed 8192 MB ceiling, so setting the heap to the full 8192 leaves V8 no headroom and can make OOM worse. On a Standard machine, 7168 is a safer cap:
# vercel.json or env var
NODE_OPTIONS="--max-old-space-size=7168"
Then bisect: temporarily comment out post-build scripts and measure how long the framework build alone takes; subtract to find the killer step. If the framework build itself genuinely needs more than 8 GB, the heap flag won’t save you — upgrade the build machine (Step 8).
Step 8: Put the build on a faster machine, or move it off Vercel
Two escape hatches for builds that are legitimately heavy:
- Bigger build machine (Pro/Enterprise). As of June 2026 Vercel offers larger build tiers in Settings → Build and Deployment → Build Machines: Enhanced (8 vCPU / 16 GB / 64 GB disk) and Turbo (30 vCPU / 60 GB), plus Elastic which auto-scales between them (the default for new paid teams). Doubling memory to 16 GB clears most OOM-related slowdowns; more vCPUs speed up bundling and type-checking. These are billed by CPU minute (Elastic starts at ~$0.0035/CPU-minute), so it’s a cost lever, not a free one — but it raises the only knob that actually changes the hardware. The 45-minute wall still applies.
- Build in CI, deploy a prebuilt artifact. For builds reliably over 30 minutes, build on a GitHub Actions runner (more time, more memory, no 45-minute wall) and ship the result with
vercel deploy --prebuilt. Move lint, tests, andtscinto that CI job too so they never count against the Vercel build budget.
How to confirm it’s fixed
- Build duration returns to within 1.5x of the last known-good build.
- The build log restores the previous cache on the second run after the fix (warm build).
- Page-generation count matches expectations — no surprise 10x.
- Each post-build script shows visible start and end markers in the log.
- A forced no-cache deploy (
vercel --force) finishes well under 45 minutes — proving the cold path is healthy, not just the warm one.
Long-term prevention
- Alert on build duration: any deploy over 1.5x the rolling-average duration should page someone. Build Diagnostics in the dashboard is the easy source for this.
- Gate all heavy
postinstalldownloads behind env vars so CI can skip them. - Print a
[build-stats] pages=N duration=Xs bundle-size=Ysummary line at the end of every build so you can grep history. - Treat any cache miss as a known cost. Because the cache is evicted after a month idle (and on Node/package-manager/branch-key changes), schedule a weekly warm-up deploy on otherwise-idle projects.
- Adopt
turboornxwith proper output caching for any monorepo over 3 packages. - Keep
getStaticPathsto top-N hot pages and fall back to ISR / on-demand for the long tail.
Common pitfalls
- Upgrading the team to Enterprise to “raise the time limit” — the 45-minute wall is identical on Enterprise. What helps on a paid plan is a faster build machine (Step 8), not a higher plan tier.
- Setting
--max-old-space-size=8192on a Standard 8 GB container — it leaves V8 no headroom and can trigger the OOM you were trying to avoid. - Adding more parallelism to a build that is already OOM-thrashing — it makes it worse.
- Caching
node_modulesaggressively but forgetting.next/cache— Next.js’s content cache is where the real speedup lives. - Disabling the cache “to get a clean build” repeatedly — every cold start is a 4-6x penalty.
- Wrapping a hanging network call in a
sleep 60retry instead of a hardtimeout. See Vercel stuck building for related hang patterns.
FAQ
Q: I’m on Pro. Can I extend the build limit past 45 minutes?
No. 45 minutes is the platform cap on Hobby, Pro, and Enterprise alike (June 2026). You can make the build finish faster with a larger build machine (Step 8), but you can’t raise the wall. Builds that genuinely need more than 45 minutes have to be split — monorepo splitting, ISR fallback, or a pre-build job in CI that ships a --prebuilt artifact.
Q: My build cache restores but the build is still slow.
Cache restore only helps frameworks that wrote into the cache directory. Make sure .next/cache, .astro/, and node_modules/.cache/ are populated by your framework and not wiped by a clean step. Also check vercel.json for a buildCommand that calls rm -rf somewhere.
Q: Should I move the build to GitHub Actions and just deploy artifacts to Vercel?
For builds reliably over 30 minutes, yes. Build on a CI runner with more time and memory, then run vercel deploy --prebuilt. See Vercel build failed for the standard Vercel-side flow.
Q: Why was my cache evicted — can I see the event?
Vercel doesn’t surface cache LRU events directly, but the rules are concrete. The cache is retained for one month, then evicted if idle. It’s also invalidated whenever the cache key changes, and that key is derived from your account/team, project, Framework Preset, Root Directory, Node.js version, package manager, and Git branch. So a Node major bump, switching npm↔pnpm, or building a brand-new branch with no parent production cache all force a cold build. Plan accordingly.
Q: How big can the build cache get, and can I control what’s cached?
Each project’s build cache is capped at 1 GB and you can’t manually configure which files are cached — Vercel caches node_modules/** plus your framework’s known cache directories based on the Framework Preset. If your cache is near 1 GB, trim oversized artifacts (run with VERCEL_BUILD_SYSTEM_REPORT=1 to see what’s eating disk) so the cache stays useful.
Tags: #Troubleshooting #Vercel #Build #timeout #CI