Initializing Enclave...

How to Fix Webpack 'Module not found: Can't resolve process' in Client-Side Bundles

Threat/Impact Level: HIGH | Exploitability/Downtime Risk: HIGH | Time to Fix: 10 mins

TL;DR

  • What broke: Webpack 5 deliberately removed automatic Node.js core module polyfills. Any code — yours or a dependency — that references process, process.env, or process.browser at runtime in the browser will hard-fail at compile time.
  • How to fix it: Inject process as a global via webpack.ProvidePlugin pointing to the process/browser package, or replace all process.env.* references statically using webpack.DefinePlugin. Do not use resolve.fallback alone — it resolves the module but does not inject it as a global.
  • Shortcut: Use our Client-Side Sandbox below to auto-refactor this — paste your webpack.config.js and get a patched version without sending your config to a third-party server.

The Incident (What Does the Error Mean?)

You hit this during webpack build or webpack serve:

Module not found: Error: Can't resolve 'process' in '/your-project/node_modules/some-library/src'

WEBPACK_RESOLVE_EXTENSIONS: .js, .jsx, .ts, .tsx
resolve 'process' in '/your-project/node_modules/some-library/src'
  No description file found
  no extension
    /your-project/node_modules/some-library/src/process doesn't exist

Immediate consequence: The build fails completely. Nothing ships. In a CI/CD pipeline this kills the deployment. In local dev it blocks every engineer on the team until patched. The error is non-recoverable at runtime — Webpack refuses to emit a bundle.

This is not a runtime crash you can catch with a try/catch. It is a compile-time resolution failure.


The Attack Vector / Blast Radius

This is a build-breaking regression, not a security exploit — but the blast radius is severe:

  1. Dependency-induced failures: You may not own the offending code. A transitive dependency (e.g., axios, cross-fetch, older versions of dotenv, debug) may reference process.env.NODE_ENV internally. Upgrading any one package can silently introduce this failure.
  2. Environment variable leakage risk (secondary): The naive fix developers reach for — installing process and adding resolve.fallback: { process: require.resolve('process/browser') } without ProvidePlugin — results in process being bundled but not globally injected. Developers then hardcode process.env.API_KEY directly in source, assuming it's server-only. It is not. DefinePlugin inlines these values as plaintext strings into the emitted JS bundle. Any process.env.SECRET_KEY baked in via DefinePlugin is readable by anyone who downloads your JS.
  3. CI/CD pipeline stall: Every downstream stage — integration tests, Lighthouse audits, container image builds — is blocked. In a trunk-based development shop, this can affect every engineer pushing to main.

How to Fix It (The Solution)

Install the polyfill package first

npm install process

Basic Fix — ProvidePlugin (inject process as a global)

Use this when a library you don't control references process globally.

// webpack.config.js
 const webpack = require('webpack');

 module.exports = {
   resolve: {
-    // No fallback configured — Webpack 5 won't auto-polyfill
+    fallback: {
+      process: require.resolve('process/browser'),
+    },
   },
   plugins: [
+    new webpack.ProvidePlugin({
+      process: 'process/browser',
+    }),
   ],
 };

Why both? resolve.fallback tells the module resolver where to find process when it's require()'d explicitly. ProvidePlugin auto-imports process as a free variable whenever Webpack sees it referenced — which is what most libraries actually do.


Enterprise Best Practice — DefinePlugin (static replacement, zero runtime cost)

Use this when your own code uses process.env.NODE_ENV or specific env vars, and you want zero-overhead static inlining with no polyfill bundle weight.

// webpack.config.js
 const webpack = require('webpack');

 module.exports = {
   plugins: [
-    // process.env.NODE_ENV referenced in source with no replacement
+    new webpack.DefinePlugin({
+      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'),
+      // ONLY define what the browser bundle explicitly needs.
+      // DO NOT inline secrets. Use a secrets manager at deploy time.
+      'process.env.REACT_APP_API_URL': JSON.stringify(process.env.REACT_APP_API_URL),
+    }),
   ],
 };

⚠️ Critical: DefinePlugin does a literal text substitution at build time. If you inline process.env.DATABASE_PASSWORD, that string appears verbatim in your public JS bundle. Treat every DefinePlugin key as public. Use REACT_APP_ or NEXT_PUBLIC_ prefixes as a forcing function — they signal "this is intentionally client-exposed."


When you need BOTH (most real-world cases)

// webpack.config.js
 const webpack = require('webpack');

 module.exports = {
+  resolve: {
+    fallback: {
+      process: require.resolve('process/browser'),
+    },
+  },
   plugins: [
+    new webpack.ProvidePlugin({
+      process: 'process/browser',
+    }),
+    new webpack.DefinePlugin({
+      'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV || 'production'),
+    }),
   ],
 };

💡 Tired of pasting proprietary configs into ChatGPT? Generic AI tools log your company's ARNs, DB strings, and private keys. StackEngine is a zero-backend, pure Client-Side WASM utility. Drop your failing config into the sandbox above. We redact your secrets locally in the browser and auto-generate the refactored code using your own API key.


Prevention in CI/CD

1. Enforce at build time with a postinstall audit

Add to package.json:

"scripts": {
  "postinstall": "node scripts/check-webpack-polyfills.js"
}

The script checks that webpack.config.js contains ProvidePlugin or DefinePlugin before any dev even runs npm install.

2. ESLint rule — ban raw process.env in client source

npm install --save-dev eslint-plugin-node
// .eslintrc.json
{
  "rules": {
    "node/no-process-env": "error"
  }
}

This forces all environment access through a typed config module (src/config.ts) that you control, rather than scattered process.env.* calls that silently break in browser contexts.

3. Webpack Bundle Analyzer in CI — catch polyfill bloat

npm install --save-dev webpack-bundle-analyzer

Run in CI with ANALYZE=true npm run build. If process/browser shows up as unexpectedly large, a dependency is pulling in more Node polyfills than expected — investigate before merge.

4. Pin Webpack 5 migration in your ADR

Document in your Architecture Decision Record that Webpack 5 requires explicit polyfill configuration. New engineers hit this error because they copy configs from Webpack 4 tutorials. A one-line comment in webpack.config.js prevents a 2-hour debugging session:

// Webpack 5: Node core modules are NOT auto-polyfilled. See ADR-0042.

Related Diagnostics

"Part of the Syntax Utility Matrix."

View all 153 Syntax Tools →