Module not found: Error: Can't resolve 'fs'

Webpack 5 'Can't resolve fs' Error — Fix for Browser Builds

Webpack 5 dropped Node polyfills, so any browser bundle touching 'fs' breaks. Here's how to stub it out or find the real culprit.

Quick answer

Set resolve.fallback.fs = false in your webpack config. If that's not enough, find the dependency actually importing fs — it almost always shouldn't be in a browser bundle in the first place.

Why this error shows up now

Webpack 4 quietly shipped polyfills for Node's core modules. If your code or a dependency did require('fs'), webpack 4 handed back a browser shim and everything kept moving. Webpack 5 removed all of that. No more automatic polyfills for fs, path, crypto, os, stream — none of it. The team's reasoning was sound: most of these shims were bloating bundles for code paths that never ran in the browser anyway. The side effect is that anything that used to silently resolve now throws Module not found: Error: Can't resolve 'fs' the moment you upgrade.

The typical trigger I see in the wild: someone bumps webpack from 4 to 5 in a Next.js or Vue CLI project, or updates a transitive dep like dotenv, winston, or an old version of csv-parse, and suddenly the build dies with a wall of "can't resolve" errors. Nine times out of ten the offending import is buried three levels deep in node_modules and nobody on the team has ever seen that file.

The fix that works for 90% of cases

  1. Open webpack.config.js (or webpack.renderer.config.js, or wherever your browser target lives).

  2. Find the resolve block. If you don't have one, add it at the top level of the config object.

  3. Add a fallback entry that tells webpack to resolve fs to an empty module:

    module.exports = {
      // ...your existing config
      resolve: {
        fallback: {
          fs: false,
          path: require.resolve('path-browserify'),
          crypto: require.resolve('crypto-browserify'),
          stream: require.resolve('stream-browserify'),
          buffer: require.resolve('buffer/')
        }
      }
    };
  4. Rebuild. If fs was the only missing module, the error goes away immediately. You'll get more errors for path, crypto, etc. if the same dependency touches those — add them one at a time as they appear.

  5. If your config uses ESM (webpack.config.mjs), swap require.resolve for import.meta.resolve or just pre-import the shim packages.

What fs: false actually does

It tells webpack to substitute an empty module for that import. Any code that calls fs.readFileSync() at runtime will throw TypeError: fs.readFileSync is not a function. That's fine if the code path never runs in the browser. It is not fine if something actually tries to read a file — and if that's your case, stubbing isn't a fix, it's a delay. You'll find out in production.

If stubbing fs breaks things at runtime

You've got three real options. Pick based on what the offending import actually does.

Option 1: Mark the package as external

If the module is only used in a Node context (Electron main process, SSR, a CLI script bundled by the same config), don't bundle it:

module.exports = {
  externals: {
    fs: 'commonjs fs'
  }
};

This is right for Electron renderers where you're supposed to use contextBridge or preload scripts. It's wrong for a pure browser target — you'll just get a runtime error instead of a build error.

Option 2: Find and replace the dependency

Run this to see who's pulling fs in:

npx webpack --stats-error-details

The output names the exact package and file. Common offenders in 2024:

  • Old dotenv@8 — upgrade to dotenv@16
  • winston — use loglevel or pino in the browser instead
  • csv-parse@4 — use papaparse
  • Anything wrapping fs.readFile for config loading — that pattern doesn't belong in browser code

Swapping the dep is the correct fix. Stubbing is the pragmatic fix. Don't confuse the two.

Option 3: Use node-polyfill-webpack-plugin (last resort)

Everyone's first instinct is to install node-polyfill-webpack-plugin and be done. It works, but it drags in browserify shims for a dozen core modules and can add 200KB+ to your bundle. If you're shipping to the browser, that's a bad trade. Use it only when you've genuinely got browser code that needs a polyfilled core module and you can't find a lighter alternative.

const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');

module.exports = {
  plugins: [new NodePolyfillPlugin()]
};

Prevention

Two things stop this from coming back. First, add a browser field to your package.json for any package you publish that has Node-only entry points — it tells bundlers which file to use in browser targets. Second, run npx webpack --stats-error-details in CI on every build. The first time a dependency silently adds an fs import, you'll catch it in a PR rather than two weeks later when someone bumps a version on a Friday.

If you're on Next.js, don't touch the webpack config for this. Add config.resolve.fallback = { fs: false } inside next.config.js's webpack callback, or better, mark the package as server-only with the server-only npm package. Next's App Router will handle the rest.

Quick reference table

SymptomLikely causeFix
Build fails immediately after webpack 5 upgradeTransitive dep imports fsresolve.fallback.fs = false
Build passes, runtime throwsStubbed fs is actually calledReplace the dep or use externals
Electron renderer crashDirect fs use in rendererMove to preload, use contextBridge
Bundle bloat after fixnode-polyfill-webpack-pluginRemove it, stub selectively
Related Errors in Programming & Dev Tools
0X401B00EC Fix STATUS_VIDEO_DRIVER_DEBUG_REPORT_REQUEST (0x401B00EC) TypeError: Cannot read properties of undefined (reading 'map') React map() Error: Fix Undefined Property Fast 0XC01E0200 STATUS_GRAPHICS_GPU_EXCEPTION_ON_DEVICE 0xC01E0200 Fix 0X000002B4 Debugger Killed Your Process? Fix 0x2B4 Fast

Was this solution helpful?

EP
Erropedia Team
Tech Support Editors
The Erropedia editorial team researches and documents real-world tech errors from across Windows, Linux, macOS, networking, databases, cloud platforms, and more. Every solution is reviewed for accuracy and updated as software and systems evolve.