Fix: Next.js Module not found: Can't resolve 'fs' (or 'path', 'crypto', 'net')
Part of: React & Frontend Errors
Quick Answer
How to fix Next.js Module not found Can't resolve fs error caused by importing Node.js modules in client components, wrong server/client boundaries, and missing polyfills.
The Error
You build or run a Next.js application and get:
Module not found: Can't resolve 'fs'Or variations:
Module not found: Can't resolve 'path'
Module not found: Can't resolve 'crypto'
Module not found: Can't resolve 'net'
Module not found: Can't resolve 'tls'
Module not found: Can't resolve 'child_process'./node_modules/some-package/index.js
Module not found: Can't resolve 'fs'A Node.js built-in module (fs, path, crypto, etc.) is being imported in code that runs in the browser. The browser does not have access to the filesystem, network sockets, or other Node.js APIs.
Why This Happens
Next.js runs code in two environments: the server (Node.js) and the client (browser). Node.js built-in modules like fs, path, and crypto are only available on the server. When webpack tries to bundle code for the client that imports these modules, it fails because they do not exist in the browser.
The boundary is not visually obvious. In the Pages Router, the divide runs through getStaticProps, getServerSideProps, and getInitialProps — code referenced only by those functions is stripped from the client bundle, but anything imported at module top-level is bundled for both targets. In the App Router, the divide is the "use client" directive: every file without it is a Server Component, and every file with it is bundled to run in the browser. The error usually means a Client Component, directly or transitively, pulled in a module that touches Node.js built-ins.
There is a third execution target you should not forget: edge runtime. When you set export const runtime = 'edge' on a route or middleware, Next.js bundles that code for V8 isolates (Cloudflare Workers, Vercel Edge), not Node.js. Edge runtime exposes Web APIs but not the Node.js standard library, so fs, net, tls, and child_process are unavailable even though the code is technically server-side. The same Module not found: Can't resolve 'fs' text appears, but the cause is different — you put a node-only library on the edge.
Common causes:
- Importing a server-only module in a client component. Using
fsorpathin a file that renders in the browser. - A third-party package uses Node.js APIs. The package was designed for Node.js but you imported it in client-side code.
- Mixing server and client code. A utility file used by both server and client imports Node.js modules.
- Wrong component boundary in App Router. A Server Component import leaks into a Client Component.
- getStaticProps/getServerSideProps import issue. Code imported for server-side data fetching gets bundled into the client.
- Edge runtime mismatch. A route declared
runtime = 'edge'imports a library that callsfsorcrypto(Node.js variant) under the hood. - Barrel file leakage. A single
index.tsre-exports both server and client utilities, dragging Node.js imports into every consumer.
In Production: Incident Lens
This error is overwhelmingly a build-time failure — your CI pipeline blocks the deploy and nothing ships. That is the safe outcome. The dangerous variant is when the import sneaks past the bundler because the module is only referenced behind a dynamic import or a conditional path, and surfaces at runtime on an edge route.
How it surfaces in production: synthetic checks against /api/edge-handler or middleware-protected routes start returning 500s right after a deploy. The Vercel or Cloudflare logs show a runtime exception of the form Cannot find module 'node:fs' or Dynamic Code Evaluation (e.g., 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime. The Next.js page renders fine, but any request that hits the edge-bound code path errors out before reaching your handler.
Blast radius: scoped to the runtime tier that broke. If a single route handler flipped to runtime = 'edge' and pulled in a node-only dep, only that route fails. If the offending import lives in middleware.ts, the entire site is affected because middleware runs on every matched request. Static pages and Server Components on the Node runtime keep serving normally.
Monitoring signal: alert on Vercel function logs filtered by level=error plus the string Module not found or Cannot find module. Pair it with a synthetic HTTP check that hits each runtime tier (one node route, one edge route, middleware-protected path). Sentry catches the runtime variant cleanly because the unhandled rejection bubbles out of the edge handler.
Recovery sequence: the fastest path is to force the affected route back to the Node runtime. Set export const runtime = 'nodejs' (or remove the 'edge' declaration) and redeploy. This sidesteps the missing module while you investigate which dependency is the culprit. Do this before tracing the import graph — you can take 30 minutes to find the right replacement, but only after the route is serving again.
Postmortem preventive: add an ESLint rule that forbids importing fs, path, crypto, net, tls, child_process, and worker_threads from files under app/**/route.{ts,js} or middleware.ts when those files also declare edge runtime. The no-restricted-imports rule with patterns works for the static case; for the conditional case, a custom rule that inspects the runtime export alongside imports catches the rest. Add a CI step that runs next build against a known list of edge routes and greps for Cannot find module in the output — this catches transitive imports the linter misses.
Fix 1: Move Server Code to Server-Only Files
Pages Router — use getStaticProps or getServerSideProps:
// pages/index.js
import fs from "fs";
import path from "path";
// This runs ONLY on the server — never bundled for the client
export async function getStaticProps() {
const filePath = path.join(process.cwd(), "data", "posts.json");
const fileContents = fs.readFileSync(filePath, "utf8");
const posts = JSON.parse(fileContents);
return { props: { posts } };
}
// This runs on the client — no fs/path imports here
export default function Home({ posts }) {
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}getStaticProps and getServerSideProps are automatically stripped from the client bundle. Any imports they use are also excluded — but only if those imports are not used elsewhere in the file.
Pro Tip: Keep server-only imports inside
getStaticProps/getServerSidePropsor in separate files. Iffsis imported at the top of the file AND used in the component, it gets bundled for the client.
Fix 2: Use Server Components (App Router)
In Next.js 13+ App Router, Server Components run only on the server:
// app/page.tsx — Server Component by default
import fs from "fs";
import path from "path";
export default async function Home() {
const filePath = path.join(process.cwd(), "data", "posts.json");
const fileContents = fs.readFileSync(filePath, "utf8");
const posts = JSON.parse(fileContents);
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}Server Components can use fs, path, and any Node.js API. They are never sent to the client.
The error occurs when you add "use client":
"use client"; // This makes it a Client Component!
import fs from "fs"; // ERROR: Can't resolve 'fs'Remove "use client" if the component needs server-only APIs, or split the component (see Fix 3).
Fix 3: Split Server and Client Code
When a component needs both server-only APIs and client interactivity:
Server Component (fetches data):
// app/dashboard/page.tsx (Server Component)
import fs from "fs";
import { DashboardClient } from "./dashboard-client";
export default async function Dashboard() {
const data = fs.readFileSync("data/stats.json", "utf8");
const stats = JSON.parse(data);
return <DashboardClient stats={stats} />;
}Client Component (handles interactivity):
// app/dashboard/dashboard-client.tsx
"use client";
import { useState } from "react";
export function DashboardClient({ stats }) {
const [filter, setFilter] = useState("all");
return (
<div>
<select onChange={e => setFilter(e.target.value)}>
<option value="all">All</option>
<option value="active">Active</option>
</select>
<pre>{JSON.stringify(stats, null, 2)}</pre>
</div>
);
}The Server Component fetches data using Node.js APIs and passes it as props to the Client Component.
Fix 4: Fix Third-Party Package Imports
A third-party package might use Node.js APIs internally:
"use client";
import SomePackage from "some-server-package"; // Uses 'fs' internally
// ERROR: Can't resolve 'fs'Fix: Use dynamic import with ssr: false:
"use client";
import dynamic from "next/dynamic";
const SomeComponent = dynamic(() => import("some-server-package"), {
ssr: false,
});
export default function Page() {
return <SomeComponent />;
}ssr: false prevents the component from rendering on the server, so the Node.js imports are never executed.
Fix: Check if the package has a client-side version:
Many packages offer separate browser and Node.js builds:
// Instead of:
import crypto from "crypto";
// Use the Web Crypto API:
const hash = await crypto.subtle.digest("SHA-256", data);
// Or a browser-compatible package:
import { sha256 } from "js-sha256";Fix 5: Use the server-only Package
Mark files as server-only to get a clear error at build time:
npm install server-only// lib/data.ts
import "server-only";
import fs from "fs";
export function readData() {
return fs.readFileSync("data.json", "utf8");
}If a Client Component imports this file, Next.js gives a clear error:
This module cannot be imported from a Client Component module.
It should only be used from a Server Component.This is better than the confusing Can't resolve 'fs' error because it tells you exactly what is wrong.
Fix 6: Configure Webpack Fallbacks
If you need to suppress the error for a package that checks for Node.js at runtime but works without it:
next.config.js (Pages Router):
module.exports = {
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
path: false,
crypto: false,
};
}
return config;
},
};Setting a module to false replaces it with an empty module. The import resolves but any usage returns undefined.
Warning: This only works if the package handles the missing module gracefully (e.g., checks typeof window !== 'undefined' before using fs). If the package unconditionally calls fs.readFileSync(), it will fail at runtime with a different error.
For general module resolution errors, see Fix: Module not found: Can’t resolve.
Fix 7: Use API Routes for Server Operations
Instead of accessing Node.js APIs from client components, create an API route:
Pages Router:
// pages/api/data.js
import fs from "fs";
import path from "path";
export default function handler(req, res) {
const filePath = path.join(process.cwd(), "data", "posts.json");
const data = fs.readFileSync(filePath, "utf8");
res.json(JSON.parse(data));
}App Router:
// app/api/data/route.ts
import fs from "fs";
import path from "path";
import { NextResponse } from "next/server";
export async function GET() {
const filePath = path.join(process.cwd(), "data", "posts.json");
const data = fs.readFileSync(filePath, "utf8");
return NextResponse.json(JSON.parse(data));
}Client component calls the API:
"use client";
import { useEffect, useState } from "react";
export default function Posts() {
const [posts, setPosts] = useState([]);
useEffect(() => {
fetch("/api/data")
.then(res => res.json())
.then(setPosts);
}, []);
return <ul>{posts.map(p => <li key={p.id}>{p.title}</li>)}</ul>;
}Common Mistake: Importing server-only utilities in a shared file used by both server and client code. Even if the client never calls the server function, webpack still bundles the import. Split shared files into
utils.server.tsandutils.client.ts.
Fix 8: Fix Environment Variable Issues
process.env behaves differently on server and client:
// Server-only — works in Server Components and API routes
const dbUrl = process.env.DATABASE_URL;
// Client-side — must be prefixed with NEXT_PUBLIC_
const apiUrl = process.env.NEXT_PUBLIC_API_URL;Without the NEXT_PUBLIC_ prefix, environment variables are stripped from the client bundle. Accessing them returns undefined.
For environment variable issues in general, see Fix: environment variable is undefined.
Still Not Working?
Check your import tree. A single import chain from a Client Component to a file that imports fs causes the error. Trace the imports:
ClientComponent → utils → database → fs ← ERRORBreak the chain by splitting utils into server and client versions.
Check for barrel file issues. A barrel file (index.ts) that re-exports both server and client utilities causes webpack to bundle everything:
// lib/index.ts — barrel file
export { readData } from "./server-utils"; // Uses fs
export { formatDate } from "./client-utils";
// Importing formatDate also pulls in readData's dependencies:
import { formatDate } from "@/lib";Fix: Import directly from the specific file, not the barrel:
import { formatDate } from "@/lib/client-utils";Check for edge runtime declarations. A route file with export const runtime = 'edge' cannot reach Node.js built-ins even when the file looks server-side. Open every route.ts, page.tsx, layout.tsx, and middleware.ts in the affected path and confirm the runtime export. If the failing module is a node-only library (Prisma client, AWS SDK v2, nodemailer), either remove the 'edge' declaration or replace the library with an edge-compatible alternative (Prisma Accelerate, AWS SDK v3 with @aws-sdk/client-*, Resend).
Check the build output for “the module factory is not available”. This message appears when a module successfully resolved at build time but the bundled code calls a Node.js API that the runtime forbids. It is distinct from Can't resolve — it means the bundler tolerated the import but the runtime refused to execute it. The fix is the same as for the resolve error: keep the module behind a server-only boundary.
Check for monorepo path resolution. In a Turborepo or pnpm workspace, a shared package compiled with tsc may emit imports that point to source .ts files instead of compiled .js. If that shared package contains Node.js imports, every Next.js app that depends on it inherits the error. Run pnpm why <package> to confirm the resolved path and rebuild the shared package with the correct main/exports fields.
Check whether you are using require instead of import. Webpack treats require('fs') and import 'fs' differently when it comes to tree-shaking. Code paths reached only through a conditional require can still cause the resolver to fail. Convert to dynamic import() and the bundler will respect the code-splitting boundary.
For Next.js hydration issues where server and client output differ, see Fix: Next.js hydration failed. If the image optimization fails instead of module resolution, see Fix: Next.js image optimization error.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: React Hydration Error — Text Content Does Not Match
How to fix React hydration errors — server/client HTML mismatches, useEffect for client-only code, suppressHydrationWarning, dynamic content, and Next.js specific hydration issues.
Fix: Next.js Image Optimization Errors – Invalid src, Missing Loader, or Unoptimized
How to fix Next.js Image component errors including 'Invalid src prop', 'hostname not configured', missing loader, and optimization failures in production.
Fix: React useEffect runs infinitely (infinite loop / maximum update depth exceeded)
How to fix useEffect infinite loops in React — covers missing dependency arrays, referential equality, useCallback, unconditional setState, data fetching cleanup, event listeners, useRef, previous value comparison, and the exhaustive-deps lint rule.
Fix: Hydration failed because the initial UI does not match what was rendered on the server (Next.js)
How to fix the Next.js hydration mismatch error. Covers invalid HTML nesting, browser extensions, Date/time differences, useEffect for client-only code, dynamic imports, suppressHydrationWarning, localStorage, third-party scripts, Math.random, auth state, and React portals.