Yeah, that error is a classic head-scratcher, especially when the same code runs fine in Node. Let's cut to it.
The quick fix (stop importing fs)
First, ask yourself: do you actually need fs in the browser? 99% of the time, the answer is no. You're probably pulling in a library that uses fs internally, like axios (older versions did this), node-fetch, or some config parser. The real fix is to stop importing that module directly.
Search your codebase for any import fs from 'fs' or require('fs') — check your own code first, then your node_modules if you have to. If it's your own code, just remove it. If it's a dependency, you have two options:
- Find a browser-compatible alternative (e.g., use
fetchinstead offs.readFile). - Shim the module (I'll show you below).
But what if the library really needs fs?
Some libraries aren't meant for the browser, period. If you're trying to use something like fs-extra or glob in a React component, you're fighting the platform. The browser has no filesystem access by design — it's a security boundary you shouldn't try to cross. Politely tell that library to go away, or move that logic to a backend API.
Why the error happens (the boring but important part)
What's actually happening here is that your bundler — Webpack, Vite, whatever — is trying to resolve fs at build time. The bundler looks for that module in your node_modules, finds it (since Node has it as a core module), but then realizes you're targeting a browser environment where that module doesn't exist. So it throws Module not found: Can't resolve 'fs'.
In a Node server, fs exists as a built-in, so the same import works fine. The problem only appears when you're bundling for the browser. It's not that the module is missing from your disk — it's that the bundler knows it can't ship Node's fs to a browser.
Webpack used to automatically polyfill Node core modules in older versions (like 4.x), but they removed that in v5 for performance and security reasons. That's why you see this error more often on newer setups.
Shimming fs as a last resort
If you absolutely must get a build running (say, you're migrating an old Node project to the browser), you can tell your bundler to substitute a browser-compatible fake.
For Webpack 5
Add this to your webpack.config.js:
const webpack = require('webpack');
module.exports = {
resolve: {
fallback: {
fs: false, // just stub it out
path: require.resolve('path-browserify')
}
},
plugins: [
new webpack.ProvidePlugin({
process: 'process/browser',
Buffer: ['buffer', 'Buffer']
})
]
};
Setting fs: false tells Webpack to provide an empty object when anything tries to import it. That'll silence the error, but don't expect it to work. If some code actually calls fs.readFileSync, it'll throw a runtime error instead of a build error. This is a band-aid, not a cure.
For Vite
Vite is stricter. You can't just add a fallback; you need to install vite-plugin-node-polyfills and configure it:
npm install vite-plugin-node-polyfills
Then in vite.config.js:
import { nodePolyfills } from 'vite-plugin-node-polyfills';
export default {
plugins: [nodePolyfills()]
};
That plugin will shim fs, path, crypto, and others. But again — only do this if you're okay with those shims being fake. They won't read real files.
Less common variations of this error
You might also run into:
1. Can't resolve 'path' — or 'crypto', 'stream', etc.
Same root cause, different module. These are all Node core modules that don't exist in browsers. The fix is identical: stop importing them, or polyfill them with the browser equivalents.
2. The error appears in a test environment (Jest)
Jest runs in Node, but when you use a library that references fs, Jest might complain too. In that case, you can mock the module in your test file:
jest.mock('fs', () => ({
readFileSync: jest.fn(() => 'mock data')
}));
That's fine for tests, because you're testing your component, not the fs logic.
3. The error only happens on production builds
Your dev server might have a looser config (like nodeIntegration in Electron), while your production build uses a stricter browser target. If you're using Electron, you might actually have fs available in the main process but not in the renderer. The fix is to keep your main process and renderer separate — never import Node modules in the renderer.
Prevention: know your environment
The most reliable way to avoid this error is to think about where your code runs before you write it. If you're building a React app that's meant to run in a browser, assume you have no access to Node APIs. No fs, no path, no process.env (unless you wire it up with a bundler plugin).
- Use
fetchto read files from a server. - Use
localStorageorIndexedDBfor client-side persistence. - If you need to read a file the user picks, use
<input type="file">andFileReader.
Also, before you install a new npm package, check its package.json for "browser" field — that often indicates the browser-specific build. If it's not there, search for "browser compatible" before committing.
One last tip: when you hit this error, read the stack trace carefully. It'll usually point you to the exact file that's trying to import fs. That tells you where to fix it — not your bundler config, but that file.
So, in short: don't shim unless you have to. Delete the import. Move the logic to a server. Your future self will thank you when the code actually runs.