在Next.js应用程序中,找不到模块:无法解析“fs”。

121

无法确定我的 next.js 应用程序中正在发生什么。由于 fs 是 nodejs 的默认文件系统模块,因此它会给出“模块未找到”的错误。

输入图片说明

输入图片说明


1
在删除 node_modules 文件夹后,尝试进行全新的 npm install - Ajit Panigrahi
1
正如@AjitZero所说,那可能就是问题所在。您还可以删除 package-lock.json 文件,然后运行 npm cache clean 命令并执行 npm install - Krzysztof Podmokły
我删除了node_modules和package-lock.json,然后进行了npm cache clean和npm install操作...但是仍然出现相同的错误。 - Ibad Shaikh
6
您无法在浏览器中使用 fs 模块,因为它是 Node.js 模块。 - Talg123
确保你没有在浏览器中使用它。最近我就犯了这个错误……我忘记了,在浏览器中导入了它。花了我几天时间才解决这个问题 :/ - Master Ace
请确保在Next.js页面中使用getServerSidePropsgetStaticPropsgetInitialProps等,而不是普通组件内部。我曾犯过这个错误,浪费了一些时间。 - Manpreet
18个回答

131

如果您使用fs,请确保仅在getInitialPropsgetServerSideProps内部使用,(包括任何服务器端渲染)。

您还需要创建一个next.config.js文件,并添加以下内容才能构建客户端捆绑包:

对于 webpack4

module.exports = {
  webpack: (config, { isServer }) => {
    // Fixes npm packages that depend on `fs` module
    if (!isServer) {
      config.node = {
        fs: 'empty'
      }
    }

    return config
  }
}

对于webpack5

module.exports = {
  webpack5: true,
  webpack: (config) => {
    config.resolve.fallback = { fs: false };

    return config;
  },
};

注意:对于其他模块(例如path),您可以添加多个参数,例如

{
  fs: false,
  path: false
}

3
黄金答案!只是我还需要在fs:false之后添加 path:false - Gabriel Arghire
1
由于问题仅涉及到 fs,@JusticeBringer。我可能无法添加代码,但让我将其作为注释添加。 - Arjun Kava
1
仍然无法使用 next@^12.3.1 - cwtuan
如果只在仅由getStaticProps调用的函数中使用fs,是否可能?对我来说似乎无法工作。 - Jeffmagma
1
@Jeffmagma 是的,如果你导入了 fs 但没有在 getStaticPathsgetStaticProps(以及其他仅限服务器的默认方法)中使用它,那么你将会看到 OP 中的错误。 - TimPietrusky

51

我花了很多时间才找到解决方案,Stackoverflow 上也有相关的答案,链接如下:https://dev59.com/questions/_cDqa4cB1Zd3GeqPh6FU#67478653

在此,我请求 MOD 允许分享这个问题,因为这是谷歌上第一个显示的问题,可能会有越来越多的人遇到同样的问题,所以我想帮助他们省下一些麻烦。

因此,在你的 next.config.js 中需要添加以下代码:

    module.exports = {
  future: {
    webpack5: true, // by default, if you customize webpack config, they switch back to version 4. 
      // Looks like backward compatibility approach.
  },
  webpack(config) {
    config.resolve.fallback = {
      ...config.resolve.fallback, // if you miss it, all the other options in fallback, specified
        // by next.js will be dropped. Doesn't make much sense, but how it is
      fs: false, // the solution
    };

    return config;
  },
};

对我来说,它的效果就像魔法一样好。


40

最小可复现示例

一个干净的最小示例对于Webpack初学者非常有益,因为基于使用情况的自动分割是如此令人惊叹的魔法。

工作的Hello World基线:

pages/index.js

// Client + server code.

export default function IndexPage(props) {
  return <div>{props.msg}</div>
}

// Server-only code.

export function getStaticProps() {
  return { props: { msg: 'hello world' } }
}

package.json

{
  "name": "test",
  "version": "1.0.0",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start"
  },
  "dependencies": {
    "next": "12.0.7",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  }
}

运行方式:

npm install
npm run dev

现在我们添加一个虚拟的 require('fs') 来制造麻烦:
// Client + server code.

export default function IndexPage(props) {
  return <div>{props.msg}</div>
}

// Server-only code.

const fs = require('fs')

export function getStaticProps() {
  return { props: { msg: 'hello world' } }
}

出现以下错误:

Module not found: Can't resolve 'fs' 

这并不太奇怪,因为Next.js无法知道fs仅适用于服务器端,我们也不希望它忽略随机的require错误,对吧?只有getStaticProps是一个硬编码的Next.js函数名,Next.js才知道它的作用。
好的,那么让我们在getStaticProps中使用fs来告诉Next.js,以下内容再次可行:
// Client + server code.

export default function IndexPage(props) {
  return <div>{props.msg}</div>
}

// Server-only code.

const fs = require('fs')

export function getStaticProps() {
  fs
  return { props: { msg: 'hello world' } }
}

震惊了!我们理解,getStaticProps 函数体中任何提到 fs 的地方,即使像上面那样毫无用处,Next.js/Webpack 也会明白它只能在服务器端运行。

getServerSidePropsgetStaticPaths 同理。

高阶组件(HOC)必须放在自己的文件中

现在,我们将 IndexPagegetStaticProps 从不同但类似的页面中分离出来的方法是使用 HOCs,它们只是返回其他函数的函数。

HOCs 通常会放在 pages/ 之外,然后从多个位置引用,但当您要将事物分离以进行泛化时,可能会尝试将它们直接放入 pages/ 文件中,例如:

// Client + server code.

import Link from 'next/link'

export function makeIndexPage(isIndex) {
  return (props) => {
    return <>
      <Link href={isIndex ? '/index' : '/notindex'}>
        <a>{isIndex ? 'index' : 'notindex'}</a>
      </Link>
      <div>{props.fs}</div>
      <div>{props.isBlue}</div>
    </>
  }
}

export default makeIndexPage(true)

// Server-only code.

const fs = require('fs')

export function makeGetStaticProps(isBlue) {
  return () => {
    return { props: {
      fs: Object.keys(fs).join(' '),
      isBlue,
    } }
  }
}

export const getStaticProps = makeGetStaticProps(true)

但是,如果您这样做,您会感到悲伤,因为您会看到:
Module not found: Can't resolve 'fs' 

所以我们明白了另一件事情:在getStaticProps函数体内直接使用fs,Webpack无法在子函数中捕获它。
唯一的解决方法是将仅限于后端的内容放在单独的文件中,例如:

pages/index.js

// Client + server code.

import { makeIndexPage } from "../front"

export default makeIndexPage(true)

// Server-only code.

import { makeGetStaticProps } from "../back"

export const getStaticProps = makeGetStaticProps(true)

pages/notindex.js

// Client + server code.

import { makeIndexPage } from "../front"

export default makeIndexPage(false)

// Server-only code.

import { makeGetStaticProps } from "../back"

export const getStaticProps = makeGetStaticProps(false)

front.js

// Client + server code.

import Link from 'next/link'

export function makeIndexPage(isIndex) {
  return (props) => {
    console.error('page');
    return <>
      <Link href={isIndex ? '/notindex' : '/'}>
        <a>{isIndex ? 'notindex' : 'index'}</a>
      </Link>
      <div>{props.fs}</div>
      <div>{props.isBlue}</div>
    </>
  }
}

back.js

// Server-only code.

const fs = require('fs')

export function makeGetStaticProps(isBlue) {
  return () => {
    return { props: {
      fs: Object.keys(fs).join(' '),
      isBlue,
    } }
  }
}

Webpack必须看到将名称makeGetStaticProps分配给getStaticProps,因此它决定整个back文件仅适用于服务器端。
请注意,如果您尝试将back.jsfront.js合并为单个文件,则无法正常工作,可能是因为当您执行export default makeIndexPage(true)时,Webpack必然会尝试将整个front.js文件拉入前端,其中包括fs,因此失败了。
这导致库文件自然而然地(基本上几乎是强制性的)分为以下两类:
  • front.jsfront/*:前端+后端文件。这些对前端是安全的。而且后端可以做任何前端可以做的事情(我们正在进行SSR吗?),因此这些也可从后端使用。

    也许这就是许多官方示例中常规“components”文件夹的想法所在。但那是一个糟糕的名字,因为该文件夹不仅应该包含组件,还应包含任何将从前端使用的库非组件助手/常量。

  • back.jsback/*(或者替代front/*之外的任何内容):仅后端文件。这些只能由后端使用,将其导入前端将导致错误。


1
先生,您是我的英雄。这个最小化的复现示例正是我需要理解问题的。老实说,看到代码消除如此愚蠢,并且它不会遍历代码比纯字符串匹配更远,真的很烦人。 - Danielo515

17

虽然这个错误需要比大多数错误更多的推理,但它出现是有一个简单明了的原因。

为什么会出现这种情况

与许多框架不同,Next.js允许你在页面文件中导入仅服务器端可用的(不适用于浏览器的Node.js API)代码。当Next.js构建你的项目时,它通过检查哪些代码存在于以下任何一个内置方法中(代码分割)来从客户端捆绑包中删除仅服务器端可用的代码:

  • getServerSideProps
  • getStaticProps
  • getStaticPaths

附注:有一个演示应用程序可以可视化展示这个过程。

Module not found: can't resolve 'xyz'错误发生在你尝试在这些方法之外使用仅服务器端可用的代码时。

错误示例1 - 基础版

为了重现这个错误,让我们从一个正常工作的简单的Next.js页面文件开始。

正常工作的文件

/** THIS FILE WORKS FINE! */

import type { GetServerSideProps } from "next";

import fs from "fs"; // our server-only import

type Props = {
  doesFileExist: boolean;
};

export const getServerSideProps: GetServerSideProps = async () => {
  const fileExists = fs.existsSync("/some-file"); 

  return {
    props: {
      doesFileExist: fileExists,
    },
  };
};

const ExamplePage = ({ doesFileExist }: Props) => {
  return <div>File exists?: {doesFileExist ? "Yes" : "No"}</div>;
};

export default ExamplePage;

现在,让我们将fs.existsSync方法从getServerSideProps中移出来,以重现错误。虽然这个差异微妙,但是下面的代码将会抛出我们可怕的Module not found错误。 ERROR文件
import type { GetServerSideProps } from "next";
import fs from "fs";

type Props = {
  doesFileExist: boolean;
};

/** ERROR!! - Module not found: can't resolve 'fs' */
const fileExists = fs.existsSync("/some-file");

export const getServerSideProps: GetServerSideProps = async () => {
  return {
    props: {
      doesFileExist: fileExists,
    },
  };
};

const ExamplePage = ({ doesFileExist }: Props) => {
  return <div>File exists?: {doesFileExist ? "Yes" : "No"}</div>;
};

export default ExamplePage;

错误示例2 - 真实情况

这种错误最常见(也最令人困惑)的情况是在使用包含多种类型代码(客户端+服务器端)的模块时发生。

假设我有一个名为file-utils.ts的模块,其中包含以下内容:

import fs from 'fs'

// This code only works server-side
export function getFileExistence(filepath: string) {
  return fs.existsSync(filepath)
}

// This code works fine on both the server AND the client
export function formatResult(fileExistsResult: boolean) {
  return fileExistsResult ? 'Yes, file exists' : 'No, file does not exist'
}

在这个模块中,我们有一个仅限服务器使用的方法和一个“共享”方法,理论上应该可以在客户端工作(但正如我们将看到的那样,理论并不完美)。
现在,让我们尝试将其整合到我们的Next.js页面文件中。
/** ERROR!! */

import type { GetServerSideProps } from "next";

import { getFileExistence, formatResult } from './file-utils.ts'

type Props = {
  doesFileExist: boolean;
};

export const getServerSideProps: GetServerSideProps = async () => {
  return {
    props: {
      doesFileExist: getFileExistence('/some-file')
    },
  };
};

const ExamplePage = ({ doesFileExist }: Props) => {

  // ERROR!!!
  return <div>File exists?: {formatResult(doesFileExist)}</div>;
};

export default ExamplePage;

如您所见,我们在尝试在客户端使用formatResult时出现错误,因为我们的模块仍然需要导入服务器端代码
为了解决这个问题,我们需要将我们的模块分成两类:
  1. 仅限服务器
  2. 共享代码(客户端或服务器)
// file-utils.ts

import fs from 'fs'

// This code (and entire file) only works server-side
export function getFileExistence(filepath: string) {
  return fs.existsSync(filepath)
}

// file-format-utils.ts

// This code works fine on both the server AND the client
export function formatResult(fileExistsResult: boolean) {
  return fileExistsResult ? 'Yes, file exists' : 'No, file does not exist'
}

现在,我们可以创建一个可用的页面文件:
/** WORKING! */

import type { GetServerSideProps } from "next";

import { getFileExistence } from './file-utils.ts' // server only
import { formatResult } from './file-format-utils.ts' // shared

type Props = {
  doesFileExist: boolean;
};

export const getServerSideProps: GetServerSideProps = async () => {
  return {
    props: {
      doesFileExist: getFileExistence('/some-file')
    },
  };
};

const ExamplePage = ({ doesFileExist }: Props) => {
  return <div>File exists?: {formatResult(doesFileExist)}</div>;
};

export default ExamplePage;

解决方案

有两种方法可以解决这个问题:

  1. “正确”的方法
  2. “让它工作” 的方法

“正确”的方法

解决此错误的最佳方式是确保您理解为什么会发生这种情况(上文提到),并且确保只在 getStaticPathsgetStaticPropsgetServerSideProps 中使用服务器端代码,而且不要在其他任何地方使用

请记住,如果您导入一个包含服务器端和客户端代码的模块,则无法在客户端使用该模块中的任何导入内容(参见上面的示例#2)。

“让它工作” 的方法

正如其他人建议的那样,您可以修改 next.config.js 来在构建时忽略某些模块。这意味着当 Next.js 尝试在页面文件之间拆分仅服务器端共享代码时,它不会尝试填充构建客户端失败的 Node.js API。

在这种情况下,您只需要:

/** next.config.js - with Webpack v5.x */
module.exports = {

  ... other settings ... 

  webpack: (config, { isServer }) => {
    
    // If client-side, don't polyfill `fs`
    if (!isServer) {
      config.resolve.fallback = {
        fs: false,
      };
    }

    return config;
  },

};

该方法的缺点

正如 Webpack 文档中的 resolve.fallback 部分所示,使用此配置选项的主要原因是因为自 Webpack v5.x 起,默认情况下不再对核心 Node.js 模块进行 polyfill。因此,此选项的主要目的是提供一种定义您想使用哪个 polyfill 的方式。

当您将选项设置为 false 时,这意味着“不包括 polyfill”。

虽然这样可以实现功能,但它可能会变得脆弱,并需要持续的维护才能包含您引入到项目中的任何新模块。除非您正在转换现有项目/支持旧代码,否则最好选择上面的选项 #1,因为它根据 Next.js 实际如何在幕后拆分代码来推进更好的模块组织。


谢谢。在我的情况下,我从单个index.ts文件中导出了file-utils.tsfile-format-utils.ts。接下来,我猜测将服务器端和客户端代码合并成了一个不可操作的块。最终,在index.ts中删除file-format-utils.tsexport解决了问题。 - stakolee
你的第一个假设是错误的。不仅仅是当你试图在受保护的方法之外使用服务器端代码时,编译器太愚蠢了,它只能从你所在的直接文件中删除代码,如果你尝试以任何方式重构代码(例如,使用高阶组件),它将无法理解要删除哪些代码。你不需要执行这样的代码,一旦你需要它,你就注定会失败。 - Danielo515
说得好,@Zach! - chale_brown
非常感谢你付出的努力,写了这么好的回答,你真是救了我的一天。 - George Nabil
如何在next-auth和node mailer中使用它,这些包总是引起这样的问题。 - Nicholas Jela

14

fspath或其他node原生模块只能在服务器端代码内使用,例如“getServerSide”函数。如果您试图在客户端中使用它,即使您只是console.log它,也会出现错误。该console.log也应在服务器端函数内运行。

当您导入并在服务器端使用“fs”时,next.js足够聪明,可以看到您在服务器端使用它,因此不会将该导入添加到客户端包中。

我使用的某个软件包给了我这个错误,我通过下列方式进行了修复:

module.exports = {
 
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.resolve.fallback.fs = false
    }

    return config
  },
  
}

但是这会在终端上产生警告:

"Critical dependency: require function is used in a way in which

 dependencies cannot be statically extracted"

然后我尝试在浏览器上加载节点模块。 我从node_modules中复制了节点模块的“min.js”,并将其放置在“public/js/myPackage.js”中,并使用Script加载它

export default function BaseLayout({children}) {
  return (
    <>
      <Script
        // this in public folder
        src="/js/myPackage.js"
        // this means this script will be loaded first
        strategy="beforeInteractive"
      />
    </>
  )
}

这个包被附加到window对象上,并且在node_modules源代码的index.js中:

if (typeof window !== "undefined") {
  window.TruffleContract = contract;
}

所以我可以通过window.TruffleContract访问这个脚本。但是这并不是一种有效的方式。


4
如果想在Next.js中使用fs-extra,请按照以下方式操作,这对我来说有效:
module.exports = {
  webpack: (config) => {
    config.resolve.fallback = { fs: false, path: false, stream: false, constants: false };
    return config;

  }
}

1
End with return config; - Adam Beleko

3
/** @type {import('next').NextConfig} */
module.exports = {
  reactStrictMode: false,
  webpack5: true,
  webpack: (config) => {
    config.resolve.fallback = {
      fs: false,
      net: false,
      dns: false,
      child_process: false,
      tls: false,
    };

    return config;
  },
};

这段代码解决了我的问题,我想分享一下。将这段代码添加到您的next.config文件中。我正在使用webpack5。

2

我在我的NextJS应用中遇到了这个错误,因为我忘记在代码中添加export关键字。

export function getStaticProps()

3
我曾经遇到过一个相关的错误,但我错误地拼写了 "getServerSideProps",因此在客户端使用了fs模块。这是一个很容易犯的错误! - Aleksai Losey

1
在我的情况下,这个错误出现在我重构 Next.js 页面的身份验证流程时。原因是我还没有删除一些未使用的导入。
之前,我将页面设置为受保护的路由,如下所示:
export async function getServerSideProps ({ query, req, res }) {
  const session = await unstable_getServerSession(req, res, authOptions)
  if (!session) {
    return {
      redirect: {
        destination: '/signin',
        permanent: false,
      },
    }
  }
//... rest of server-side logic
}

在重构期间,我阅读了与NextAuth useSession相关的内容。 根据我的理解,我能够更改实现,只需添加 MyComponent.auth = true 就可以使页面受到保护。 然后,我删除了 getServerSideProps 中的上述代码块。 但是,我尚未删除所述代码块使用的两个导入:
import { unstable_getServerSession } from 'next-auth/next'
import { authOptions } from 'pages/api/auth/[...nextauth]'

我相信这两个导入中的第二个是引起问题的原因。所以总结一下,除了以上所有很好的答案之外,它也可能是一个未使用的导入。


1

对于我来说,清除缓存 npm cache clean -f

然后将 Node 版本更新为最新的稳定版本(14.17.0)即可解决问题。


这对我有用。已投票。 - rottitime

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接