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, orprocess.browserat runtime in the browser will hard-fail at compile time. - How to fix it: Inject
processas a global viawebpack.ProvidePluginpointing to theprocess/browserpackage, or replace allprocess.env.*references statically usingwebpack.DefinePlugin. Do not useresolve.fallbackalone — 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.jsand 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:
- Dependency-induced failures: You may not own the offending code. A transitive dependency (e.g.,
axios,cross-fetch, older versions ofdotenv,debug) may referenceprocess.env.NODE_ENVinternally. Upgrading any one package can silently introduce this failure. - Environment variable leakage risk (secondary): The naive fix developers reach for — installing
processand addingresolve.fallback: { process: require.resolve('process/browser') }withoutProvidePlugin— results inprocessbeing bundled but not globally injected. Developers then hardcodeprocess.env.API_KEYdirectly in source, assuming it's server-only. It is not.DefinePlugininlines these values as plaintext strings into the emitted JS bundle. Anyprocess.env.SECRET_KEYbaked in viaDefinePluginis readable by anyone who downloads your JS. - 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:
DefinePlugindoes a literal text substitution at build time. If you inlineprocess.env.DATABASE_PASSWORD, that string appears verbatim in your public JS bundle. Treat everyDefinePluginkey as public. UseREACT_APP_orNEXT_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.