如果您使用fs
,请确保仅在getInitialProps
或getServerSideProps
内部使用,(包括任何服务器端渲染)。
您还需要创建一个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
}
fs:false
之后添加 path:false
。 - Gabriel Arghirefs
,@JusticeBringer。我可能无法添加代码,但让我将其作为注释添加。 - Arjun Kavanext@^12.3.1
。 - cwtuanfs
但没有在 getStaticPaths
或 getStaticProps
(以及其他仅限服务器的默认方法)中使用它,那么你将会看到 OP 中的错误。 - TimPietrusky我花了很多时间才找到解决方案,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;
},
};
对我来说,它的效果就像魔法一样好。
最小可复现示例
一个干净的最小示例对于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'
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 也会明白它只能在服务器端运行。
getServerSideProps
和 getStaticPaths
同理。
高阶组件(HOC)必须放在自己的文件中
现在,我们将 IndexPage
和 getStaticProps
从不同但类似的页面中分离出来的方法是使用 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,
} }
}
}
makeGetStaticProps
分配给getStaticProps
,因此它决定整个back
文件仅适用于服务器端。back.js
和front.js
合并为单个文件,则无法正常工作,可能是因为当您执行export default makeIndexPage(true)
时,Webpack必然会尝试将整个front.js
文件拉入前端,其中包括fs,因此失败了。front.js
和front/*
:前端+后端文件。这些对前端是安全的。而且后端可以做任何前端可以做的事情(我们正在进行SSR吗?),因此这些也可从后端使用。
也许这就是许多官方示例中常规“components”文件夹的想法所在。但那是一个糟糕的名字,因为该文件夹不仅应该包含组件,还应包含任何将从前端使用的库非组件助手/常量。
back.js
和back/*
(或者替代front/*
之外的任何内容):仅后端文件。这些只能由后端使用,将其导入前端将导致错误。
虽然这个错误需要比大多数错误更多的推理,但它出现是有一个简单明了的原因。
与许多框架不同,Next.js允许你在页面文件中导入仅服务器端可用的(不适用于浏览器的Node.js API)代码。当Next.js构建你的项目时,它通过检查哪些代码存在于以下任何一个内置方法中(代码分割)来从客户端捆绑包中删除仅服务器端可用的代码:
getServerSideProps
getStaticProps
getStaticPaths
附注:有一个演示应用程序可以可视化展示这个过程。
Module not found: can't resolve 'xyz'
错误发生在你尝试在这些方法之外使用仅服务器端可用的代码时。
为了重现这个错误,让我们从一个正常工作的简单的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;
这种错误最常见(也最令人困惑)的情况是在使用包含多种类型代码(客户端+服务器端)的模块时发生。
假设我有一个名为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'
}
/** 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
时出现错误,因为我们的模块仍然需要导入服务器端代码。// 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;
有两种方法可以解决这个问题:
解决此错误的最佳方式是确保您理解为什么会发生这种情况(上文提到),并且确保只在 getStaticPaths
、getStaticProps
或 getServerSideProps
中使用服务器端代码,而且不要在其他任何地方使用。
请记住,如果您导入一个包含服务器端和客户端代码的模块,则无法在客户端使用该模块中的任何导入内容(参见上面的示例#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.ts
和file-format-utils.ts
。接下来,我猜测将服务器端和客户端代码合并成了一个不可操作的块。最终,在index.ts
中删除file-format-utils.ts
的export
解决了问题。 - stakoleefs
、path
或其他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
访问这个脚本。但是这并不是一种有效的方式。
module.exports = {
webpack: (config) => {
config.resolve.fallback = { fs: false, path: false, stream: false, constants: false };
return config;
}
}
return config;
- Adam Beleko/** @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;
},
};
我在我的NextJS应用中遇到了这个错误,因为我忘记在代码中添加export
关键字。
export function getStaticProps()
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
}
MyComponent.auth = true
就可以使页面受到保护。 然后,我删除了 getServerSideProps
中的上述代码块。 但是,我尚未删除所述代码块使用的两个导入:import { unstable_getServerSession } from 'next-auth/next'
import { authOptions } from 'pages/api/auth/[...nextauth]'
我相信这两个导入中的第二个是引起问题的原因。所以总结一下,除了以上所有很好的答案之外,它也可能是一个未使用的导入。
对于我来说,清除缓存 npm cache clean -f
然后将 Node 版本更新为最新的稳定版本(14.17.0)即可解决问题。
node_modules
文件夹后,尝试进行全新的npm install
。 - Ajit Panigrahipackage-lock.json
文件,然后运行npm cache clean
命令并执行npm install
。 - Krzysztof PodmokłygetServerSideProps
、getStaticProps
、getInitialProps
等,而不是普通组件内部。我曾犯过这个错误,浪费了一些时间。 - Manpreet