Skip to content

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.

Related questions

Why does the error name a file that does not import fs?
The bundler reports where the chain ended, not where it started. Something in that file's import graph reaches a package that requires fs, often several levels down through a shared utility.
Can I add a webpack fallback to ignore it?
You can, and the build will pass while the module resolves to nothing. Whatever needed the filesystem still does not work, and now it fails at runtime instead of at build time.