A Vercel deployment shows Building and just sits there: 15 minutes, no new log lines, no error. “Stuck building” is a different failure mode from “build failed.” Failed throws an explicit error and exits; stuck means the process is alive but making no progress.
Fastest path: cancel the build, read the last log line, and match it to the cause. If the tail says Generating static pages (X/Y) and the number stopped climbing, you have a hung fetch with no timeout — that one accounts for most stuck builds. The rest are an npm install network deadlock, an OOM GC-thrash, or a postbuild hook waiting on a webhook.
Don’t just sit and wait: as of June 2026 Vercel’s build step has a hard 45-minute maximum on every plan (Hobby, Pro, and Enterprise alike — the old “60 minutes on Pro” figure is no longer accurate). When you hit it the deployment fails with Error: The deployment's build step did not complete within the maximum of 45 minutes. Cancelling early just saves you the wait — the diagnosis is the same.
Common causes
Ordered by hit rate, highest first.
1. Static generation stuck on one page
getStaticProps / Astro’s getStaticPaths calls an external API without a timeout and the upstream hangs. Next.js and Astro process pages serially by default, so one stall blocks everything after it. The log’s last line is usually Generating static pages (XX/YY) and then nothing.
Linting and checking validity of types ...
Collecting page data ...
Generating static pages (847/3214) ...
[stuck here for 30 minutes]
How to spot it: Generating static pages (X/Y) doesn’t increment for more than 5 minutes. Almost certain.
2. npm install network deadlock
npm install tries to pull a package whose registry is unreachable (private registry, blocked CDN, 404 git URL) and retries until the build times out. The log stops at the install phase with nothing after.
npm warn deprecated ...
[no new lines for many minutes]
How to spot it: the last log segment is the install phase; you never see the framework banner (▲ Next.js, astro build, etc.) appear.
3. Build OOM — memory pegged but no crash
Not the instant Command "next build" exited with 137. Instead, heap pressure hits the ceiling, GC runs constantly, the process doesn’t die, but each step slows to near-zero. Common in large TypeScript projects with heavy type inference, webpack chunk splitting, or Turborepo parallel tasks. Every Vercel build container is fixed at 8192 MB (8 GB) of memory and 32 GB of disk on both Hobby and Pro (Hobby gets 2 CPUs, Pro 4), so you cannot buy your way out with a normal plan upgrade — see Step 4.
How to spot it: gaps between log timestamps get longer (hundreds of lines in the first 5 minutes, then 1-2 per minute). That’s GC thrashing. To confirm, set VERCEL_BUILD_SYSTEM_REPORT=1 as a project environment variable; Vercel then prints a build system report on every deployment showing peak memory, hidden OOM events, and a disk breakdown of node_modules and output.
4. Postbuild hook calling an external service hangs
package.json has "postbuild": "node scripts/notify-cdn.js" and the script fetches a webhook without a timeout, or a sitemap generator crawls the site and stalls on a slow URL.
$ next build && next-sitemap
✓ Generating static pages (1000/1000)
$ next-sitemap
[stuck here]
How to spot it: the log shows the main build finished (✓ or a build-complete banner) but the status is still Building. The postbuild step is the culprit.
5. Corrupted or mismatched build cache
Vercel restores the previous build’s cache (node_modules, framework files) before your install or build command runs. If something stale or corrupt is in there, the build can read it repeatedly and hang. The cache key is derived from team, project, framework preset, root directory, Node.js version, package manager, and Git branch; a change to any of those silently switches caches.
How to spot it: the same commit builds fine locally but hangs on Vercel, or it started right after a Node/package-manager bump.
6. Edge case: a watch/dev command in the build step
vercel.json or the Build Command was set to next dev / vite --watch by mistake. The process never exits, so it shows Building forever.
How to spot it: the log contains ready - started server on ... or watching for changes. Wrong build command.
Which bucket am I in?
| Last log line you see | Most likely cause | Jump to |
|---|---|---|
Generating static pages (X/Y) frozen | Hung fetch in static gen | Step 2 |
| Stuck in the install phase, no framework banner | Install network deadlock | Step 5 (force clean install) |
| Lines slow from hundreds/min to 1-2/min | OOM / GC thrash | Step 4 |
Build banner done (✓) but still Building | Postbuild hook hanging | check postbuild |
ready - started server / watching for changes | Dev/watch command in build | fix Build Command |
Shortest path to fix
Step 1: Cancel the build and read where the log stopped
Dashboard → your project → Deployments → the building deployment → the … menu (top right) → Cancel Build. Then open the Deployment Details section and expand the Building accordion; the last 50-100 lines of output stay there and are gold for diagnosis.
Or pull the tail from the CLI:
vercel logs <deployment-url> > stuck.log
tail -50 stuck.log
Match the tail against “Common causes” above (or the table) to identify the phase.
Step 2: Stuck in static generation — add timeouts to every external call
If the log stalled at Generating static pages (X/Y), find every untimed external call in getStaticProps / getStaticPaths / fetch(:
// Bad: no timeout, hangs the whole build if the upstream stalls
const data = await fetch(`https://api.example.com/posts/${id}`).then(r => r.json());
// Fixed: 8-second timeout with a fallback
try {
const data = await fetch(url, { signal: AbortSignal.timeout(8000) }).then(r => r.json());
return { props: { data } };
} catch (e) {
console.warn(`Skipping ${id}: ${e.message}`);
return { props: { data: null } }; // don't block the build
}
AbortSignal.timeout(8000) (Node 18+, the current Vercel default) is the one-line version of the old AbortController + setTimeout dance.
Step 3: Thousands of pages — defer them to ISR / on-demand
Don’t pre-generate thousands of static pages at build time; serial SSG of large page counts is the single most common way to walk into the 45-minute wall. Next.js:
// app or pages router — generate nothing at build, fill the cache on demand
export async function getStaticPaths() {
return {
paths: [], // pre-build nothing
fallback: 'blocking', // generate on first request, then cache
};
}
export async function getStaticProps({ params }) {
return {
props: { /* ... */ },
revalidate: 3600, // background revalidate every hour
};
}
Astro: keep output: 'server' (or hybrid on older versions) and set export const prerender = false on dynamic routes so they render on request instead of at build.
Step 4: OOM — raise the heap, watch GC, and reach for enhanced builds
First give Node more of the 8 GB the container already has and trace GC:
// package.json
{
"scripts": {
"build": "NODE_OPTIONS='--max-old-space-size=7168 --trace-gc' next build"
}
}
Keep the heap a little under the 8192 MB container limit (7168 leaves headroom for the OS and child processes) — setting it to 8192 can itself trigger the OOM kill. --trace-gc prints every GC pause to the log so you can see directly whether GC thrashing is the bottleneck.
If 8 GB genuinely isn’t enough, a normal Pro plan won’t help — the container is 8 GB on Hobby and Pro alike. Pro and Enterprise can turn on larger build machines (Enhanced builds give roughly 8 CPUs, 16 GB memory, and 58 GB disk) in Settings → Builds, billed per build minute. Otherwise, move the heavy work (type-checking, large codegen) out of the build into a separate CI job.
Step 5: Clear the build cache and redeploy clean
Dashboard → next to the deployment, … → Redeploy, then uncheck Use existing Build Cache. That forces a fresh install and build.
CLI equivalents:
vercel --force # deploy without the build cache
Or set the env var VERCEL_FORCE_NO_BUILD_CACHE=1 on the project to skip the cache on every build until you remove it. If clearing the cache fixes it, the cache was the problem; bust the key on purpose after risky changes (a Node version bump, a package-manager switch) since those already rotate the key.
Step 6: Make the build fail fast instead of hanging 45 minutes
While you iterate, wrap the build command with timeout so a hung build dies in minutes with a clear exit code instead of burning the full window:
# Build Command (Settings → Build & Development, or vercel.json "buildCommand")
timeout 15m next build
When it trips you get Error: Command "timeout 15m next build" exited with 124 (124 is the standard timeout exit code) within 15 minutes, so each debugging round is fast. Set it back to plain next build once the build is healthy.
How to confirm it’s fixed
- The deployment reaches Ready, not just “Building → cancelled.”
- In the logs,
Generating static pages (X/Y)runs to completion and the total build time is back under your normal baseline. - If you were chasing OOM, the
VERCEL_BUILD_SYSTEM_REPORT=1report shows peak memory comfortably below 8192 MB and no OOM event. - Trigger one more deploy with the cache restored; if it stays fast, the corrupt-cache theory is confirmed and resolved.
Prevention
- Force every fetch in
getStaticProps/getStaticPathsto useAbortSignal.timeout(8000). - Beyond ~200 pages, switch to ISR / on-demand; don’t bulk-generate at build time.
- Add a build-duration alert in CI: fail when duration exceeds 1.5x the rolling average, which forces optimization before you hit the 45-minute wall.
- Give every postbuild script an explicit timeout and a clean
process.exit(0). - Keep
VERCEL_BUILD_SYSTEM_REPORT=1on during a noisy period so OOM and disk creep show up before they stall a build. - Track deployment duration trends; a slow upward creep is usually early tech debt.
FAQ
Is a stuck build using up my build minutes? Yes. Vercel bills build minutes for the whole time the container runs, so a build that hangs to the 45-minute limit costs the same as 45 minutes of real work. Cancel early once you’ve grabbed the log tail.
Can I raise the 45-minute build timeout?
No — 45 minutes is a hard platform maximum on every plan as of June 2026, and there’s no setting to extend it. You can only make builds shorter (ISR, fewer pages, moving work to CI) or make them fail faster with the timeout 15m next build wrapper in Step 6.
The build works fine on my laptop but hangs on Vercel. Why?
Three usual reasons: an external API your code calls is reachable from your machine but blocked from Vercel’s build network (so a no-timeout fetch hangs); a stale Vercel build cache that your local machine doesn’t have; or higher real concurrency on Vercel exposing a serial-SSG bottleneck. Reproduce with a clean checkout and rm -rf node_modules .next && npm ci && npm run build, then do a cache-free vercel --force.
How do I tell “stuck” apart from “slow”?
Watch the log timestamps. Slow builds still emit new lines, just with growing gaps. Stuck builds emit nothing new for many minutes while the spinner keeps turning. If Generating static pages (X/Y) hasn’t moved in 5 minutes, treat it as stuck.
Does cancelling a build corrupt anything? No. A cancelled build never publishes, and Vercel only updates the build cache on a successful build, so a cancel leaves the last good cache intact. Your live production deployment is untouched.