What this usually means
Next.js build errors often stem from server/client code separation issues, missing or misconfigured dependencies, or subtle Webpack misconfigurations. Many failures are silent in development—SSR build evaluates code paths that never run client-side, exposes imports that only exist in the browser, or triggers memory or CPU spikes due to unexpectedly large bundle graphs. Package mismatches between dependencies, symlinked monorepos, and code invoking browser globals can all trip up the build phase.
The first ten minutes — establish facts before touching code.
- 1Set NODE_ENV=production and run 'next build' locally—do not rely on 'next dev'.
- 2Scrutinize the exact error line and stack in .next/trace if available.
- 3Run 'npm ls <problem-package>' to verify dependency versions and presence.
- 4Add 'console.log' above the problematic imports to confirm lazy-vs-eager code execution.
- 5Check for import statements of browser-only libraries outside 'useEffect' or dynamic imports.
- 6Temporarily increase Node memory: 'NODE_OPTIONS=--max_old_space_size=4096 next build' (if OOM error).
The specific files, logs, configs, and dashboards that usually own this bug.
- searchTerminal output from 'next build'—copy the full stack trace.
- search.next/trace (if present)—find which file triggered the error.
- searchpackage.json and yarn.lock/package-lock.json for mismatched versions.
- searchnext.config.js for custom Webpack config, especially aliases or plugins.
- searchRelevant pages and components importing failing modules.
- searchCI/CD logs for differences between local and pipeline environments.
- searchnode_modules contents—check for missing or hoisted packages.
Practical causes, not theory. These are the things you will actually find.
- warningImporting browser-only APIs (window, document) outside guards or dynamic imports.
- warningMissing or mis-versioned dependencies (yarn.lock not in sync).
- warningCustom Webpack config that breaks module resolution or introduces circular dependencies.
- warningSymlinked monorepo packages not picked up due to hoisting or missing 'main' fields.
- warningUnintentional ESM/CommonJS interop issues—require vs import.
- warningExcessive build memory usage due to huge image or data imports.
Concrete fix directions. Pick the one that matches your root cause.
- buildGuard all browser-only code: wrap with 'if (typeof window !== "undefined") { ... }'.
- buildMove browser-only imports inside dynamic imports or 'useEffect'.
- buildRun 'yarn install --check-files' or 'npm ci' to sync lockfiles and node_modules.
- buildRemove or adjust custom Webpack config that rewires module aliases.
- buildExplicitly set 'type' fields in package.json for ESM/CommonJS clarity.
- buildChunk large data or image imports; exclude from default exports.
A fix you cannot prove is a guess. Close the loop.
- verifiedRun 'next build' and confirm zero errors and all warnings resolved.
- verifiedTest the built output: 'npx serve .next' and verify SSR routes.
- verifiedCheck bundle analyzer: 'ANALYZE=true next build' if bundle size was a factor.
- verifiedRe-run the build in CI to ensure reproducibility.
- verifiedDelete .next, node_modules, and re-install clean before final build test.
Things that make this bug worse or harder to find.
- warningIgnoring errors that only appear in 'next build' but not 'next dev'.
- warningManually patching node_modules without fixing the upstream cause.
- warningAssuming all code runs in the browser—forgetting SSR context.
- warningKeeping large test/data files imported into production page code.
- warningAssuming CI/CD always matches local environment (it rarely does).
Next.js Build Fails Due to Client-Side Import in SSR Component
Timeline
- 09:03Push new analytics dashboard page to main branch.
- 09:05Vercel build job fails: 'window is not defined' in /pages/dashboard/index.tsx.
- 09:07Developer tries 'next dev' locally; no error seen.
- 09:10Copies error log; sees error thrown at import time, not inside useEffect.
- 09:12Realizes Chart.js import is top-level, which references window.
- 09:15Moves Chart.js import inside dynamic import with ssr:false.
- 09:17'next build' succeeds locally and on Vercel.
I merged a feature that included a new dashboard chart and watched our CI build blow up with 'window is not defined'. Strangely, everything worked in development.
It turned out the issue was my top-level import of Chart.js, which referenced browser APIs. The error only showed up during SSR build, not in the regular dev server.
Moving the import into a dynamic import (with ssr:false) solved it instantly. That pain taught me to always double-check import locations—SSR bites hard if you ignore it.
Root cause
Top-level import of a browser-only dependency in a file processed by SSR, leading to a ReferenceError during build.
The fix
Moved the problematic import into a dynamic import wrapped with 'ssr: false' to ensure it only loads client-side.
The lesson
Never import browser-only libraries at the top of Next.js pages/components—always consider SSR and dynamic imports.
The Next.js dev server runs code predominantly in a hot-reload client context, so accidental browser-only imports don't trigger errors until SSR build time. 'next build' runs full SSR evaluation, catching problems that never emerge in dev.
You can't trust only the dev server for correctness. Always run 'next build' before merging—or you will get bitten by SSR edge cases.
Custom Webpack config in next.config.js often causes silent failures. For example, aliasing 'src' to an absolute path breaks if directory structure changes or monorepos aren't set up identically in CI.
Missing or mismatched dependencies won't always be caught by yarn—run 'yarn install --check-files' and validate lockfile sync between machines. In the worst case, wipe node_modules and rebuild.
Out-of-memory during build (exit code 137) often happens on CI where available RAM is much lower than on a dev machine. Huge imports—images, JSON, or momentarily bloated bundles—tip the scales.
To mitigate, split large data, avoid direct imports of assets, and check whether your build pipeline VM specs are sufficient (at least 4GB recommended for moderate Next.js apps).
Frequently asked questions
Why does my Next.js build fail in CI but not locally?
Common reasons: CI has less memory, missing/unsynced dependencies, or different node/npm versions. Check build logs and lockfile consistency.
How do I debug 'window is not defined' during Next.js build?
Find the offending import or code referencing 'window'. Move it inside 'useEffect', dynamic import, or add SSR guards so it doesn't run on the server.
Is it safe to ignore Webpack 'Critical dependency' warnings?
No. These signal dynamic requires or imports that can break SSR or treeshaking. Address each: refactor to static imports, or use dynamic imports with explicit paths.
How can I speed up slow Next.js builds?
Limit per-page imports, split huge bundles, avoid importing large assets directly, and run bundle analysis to spot bloat. Check for unnecessary SSR paths.