nextjs 中为什么出现服务器报错?

"use client";
import React, { useEffect, useRef } from "react";
import * as monaco from "monaco-editor";
export default function Home() {
  const containerRef = useRef(null);
  useEffect(() => {
    if (containerRef.current) {
      const editor = monaco.editor.create(containerRef.current, {
        value: 'function hello() {\n\talert("Hello world!");\n}',
        language: "javascript",
      });
      return () => {
        editor.dispose();
      };
    }
  }, []);
  return (
    <div>
      <div
        ref={containerRef}
        id="container"
        className="h-[600px] w-[800px]"
      ></div>
    </div>
  );
}

这是 nextjs 中的代码,如果执行会出现服务器端报 ReferenceError: window is not defined 的错误。但是如果把 useEffect() 内的代码注释掉,就没有报错。
useEffect() 内的代码不是只能在客户端中执行吗?为什么执行时会导致服务器端报错?

阅读 264
avatarAI BotBETA

回答

在 Next.js 中,尽管 useEffect 通常只在客户端运行,但其中的代码可能在初始的服务器端渲染(SSR)阶段被解析和执行(特别是在涉及模块导入和顶级代码执行的情况下)。在你的代码中,monaco-editor 试图在服务器端环境中访问 window 对象,而 window 对象仅在客户端浏览器环境中存在,因此在服务器端会抛出 ReferenceError: window is not defined 错误。

要解决这个问题,你可以使用 Next.js 的动态导入功能结合 noSSR 选项或 useClient 指令来确保相关代码只在客户端执行。然而,在你的代码中已经使用了 "use client"; 指令,但这可能没有正确应用或由于其他配置问题而没有生效。确保你的 Next.js 版本支持 "use client";(这是 Next.js 12 引入的特性)。

此外,确保 "use client"; 指令位于文件的顶部,紧跟在 import 语句之后,如下所示:

"use client";
import React, { useEffect, useRef } from "react";
import * as monaco from "monaco-editor";

// 组件代码

如果问题仍然存在,你可以考虑将 Monaco Editor 的初始化逻辑放在一个单独的客户端组件中,并通过动态导入该组件来确保它只在客户端渲染:

// Home.js
import React, { Suspense, useEffect, useRef } from "react";

const MonacoEditorComponent = React.lazy(() => import('./MonacoEditor')); // 客户端组件

export default function Home() {
  const containerRef = useRef(null);

  return (
    <div>
      <Suspense fallback={<div>Loading editor...</div>}>
        <div
          ref={containerRef}
          id="container"
          className="h-[600px] w-[800px]"
        >
          <MonacoEditorComponent containerRef={containerRef} />
        </div>
      </Suspense>
    </div>
  );
}

// MonacoEditor.js
// 确保这个文件只在客户端执行
"use client";
import React, { useEffect } from "react";
import * as monaco from "monaco-editor";

export default function MonacoEditor({ containerRef }) {
  useEffect(() => {
    if (containerRef.current) {
      const editor = monaco.editor.create(containerRef.current, {
        value: 'function hello() {\n\talert("Hello world!");\n}',
        language: "javascript",
      });
      return () => {
        editor.dispose();
      };
    }
  }, [containerRef]);

  return null; // 组件不渲染任何内容,只负责编辑器初始化
}

这种方法确保 Monaco Editor 的代码只在客户端执行,从而避免了服务器端的 window 对象错误。

2 个回答
✓ 已被采纳

monaco-editor 的问题在于其内部代码(如模块加载或全局变量定义)会在模块加载时直接访问 window,而不等到组件挂载。
window 对象是浏览器环境中的全局对象,而在Node环境,window 并不存在。因此,尝试在 SSR 的代码中直接访问 window 会抛出 ReferenceError: window is not defined 的错误
useEffect 本身只在客户端执行,理论上不会在服务器端引发这个问题。如果你把访问 window 的代码放在组件外部,或者在组件初始化时就调用了这些代码(而不是延迟到 useEffect 中),那么服务器端会执行这些代码,导致错误。

import * as monaco from "monaco-editor";

改成使用 import("monaco-editor") 代替直接导入 import * as monaco from "monaco-editor"

useEffect(() => {
    let editor;

    // 动态加载 monaco-editor 确保只在客户端运行
    import("monaco-editor").then((monaco) => {
      if (containerRef.current) {
        editor = monaco.editor.create(containerRef.current, {
          value: 'function hello() {\n\talert("Hello world!");\n}',
          language: "javascript",
          theme: "vs-dark", 
        });
      }
    });

    return () => {
      if (editor) {
        editor.dispose(); // 释放资源
      }
    };
  }, []);

猜测是 monaco 里面使用到 window。当你 import 但不使用的时候,它就不会被打包到最终代码里,也不会被调用,所以就没问题。但是当你使用了,虽然其实没有真的在服务器端执行,但是也不行。

看你的意思这是个页面组件?还是 Home?建议单独做成一个 client 组件,应该是没问题的。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
logo
Microsoft
子站问答
访问
宣传栏