Next.js error
Module Not Found: Can't Resolve 'fs'
The error
Module not found: Can't resolve 'fs'A Node built-in ended up in the browser bundle. The import chain is the thing to find - the file that names fs is rarely the file that caused it.
fs, net, dns, child_process, tls - Node built-ins with no browser
equivalent. Seeing one in a bundling error means a module that was supposed to
stay on the server is being bundled for the client.
The error is structural. Something crossed a boundary, and the useful work is finding what.
Follow the chain, not the filename
The reported file is where the bundler gave up. The cause is usually two or three imports earlier:
app/dashboard/chart.tsx ('use client')
└─ lib/format.ts
└─ lib/config.ts
└─ dotenv ← reads the filesystem
chart.tsx never mentions fs. It imports one formatting helper, which
imports a config module, which pulls in a package that reads a file at startup.
One innocuous import took a server dependency into the browser.
Trace it by opening each file in the chain the error prints, or by searching for the offending package name across the repository - the shared utility that appears in both a server and a client file is almost always the join.
The three fixes, in order of preference
1. Do not import it in a client component. Compute the value on the server
and pass the result down. If chart.tsx needed a formatted currency string,
it can receive the string.
2. Split the shared module. A lib/format.ts that contains both a pure
formatting function and something environment-dependent is doing two jobs.
Separate them, and the client imports only the pure half. This is the fix most
of these resolve into, and it leaves the codebase better than it found it.
3. Mark the module server-only. The server-only package turns a silent
bundling problem into a clear build error at the exact import:
import 'server-only';
export async function getSecrets() { /* ... */ }Now anything that tries to pull this into a client bundle fails immediately, naming the file that did it. Worth adding to any module that touches credentials, a database, or the filesystem - it converts a class of subtle leaks into an error.
What looks like a fix and is not
A webpack resolve.fallback that stubs fs to false. The build passes.
The module resolves to nothing, so whatever needed the filesystem now fails
silently at runtime, usually as undefined is not a function in production.
You have moved the failure later and made it harder to read.
Adding 'use client' or removing it at random until the error moves. The
error will move. Which side of the boundary your data access ends up on will be
decided by chance.
next.config externals. Occasionally correct for a genuinely
server-only package that is misdetected, and a trap otherwise: it suppresses
the diagnosis for every case, including the ones that were real.
Why this one is worth the time
An fs error is the visible version of an invisible problem. The same import
chain that dragged a Node built-in into the browser also dragged everything
else in that module - which may include an API key that was only ever read on
the server. The error is the lucky case: the bundler noticed.
Checking which server modules reach the client bundle is one of the things an audit does with the build output, and the client boundary is where the answer lives.
