You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NextJS集成react-three-fiber扩展ThreeGlobe时window未定义构建失败

解决NextJS + react-three-fiber + ThreeGlobe的window未定义构建错误

问题背景

在NextJS项目中使用react-three-fiber扩展ThreeGlobe组件时,网站可正常运行,但构建流程失败,抛出Unhandled Runtime Error: window未定义,经排查问题出在extend({ThreeGlobe})语句。

已尝试的无效方案

  • 在组件外部使用useEffect动态导入ThreeGlobe,触发「无效钩子调用」错误
  • 在Globe组件内的useEffect中执行extend,仍出现window未定义错误
  • 使用React.lazy导入ThreeGlobe,提示「Target is not a constructor」错误

正确解决方案

核心是限制ThreeGlobe及相关扩展操作仅在客户端环境执行,避免服务器端渲染时访问window对象。具体实现:

  1. 移除全局的ThreeGlobe导入和extend调用
  2. 在客户端组件内,通过动态导入加载ThreeGlobe,并在useEffect中完成extend操作,确保仅在浏览器环境执行
  3. 利用状态标记控制组件渲染时机,避免未初始化时渲染threeGlobe标签

修改后的完整代码

"use client";

import React, { useRef, useLayoutEffect, useEffect, useState } from 'react';
import { Canvas, useFrame, extend, useThree } from "@react-three/fiber";
import countries from './globe-data-min.json';
import { Color, DirectionalLight, PointLight, NoToneMapping } from "three";
import Loading from "@/app/loading";

const CameraPosition = () => {
    const { camera } = useThree();
    camera.position.z = 250;
    camera.fov = 50;
    return null;
};

const GlobeObj = () => {
    const globeRef = useRef();
    const [isGlobeReady, setIsGlobeReady] = useState(false);

    // 弧线数据
    const N = 50;
    const arcsData = [...Array(N).keys()].map(() => ({
        startLat: (Math.random() - 0.5) * 180,
        startLng: (Math.random() - 0.5) * 360,
        endLat: (Math.random() - 0.5) * 180,
        endLng: (Math.random() - 0.5) * 360,
        color: ['#7b2080', 'white', '#4fa1e1', '#c21bcb'][Math.round(Math.random() * 3)]
    }));

    useEffect(() => {
        // 动态导入ThreeGlobe并执行extend
        import("three-globe").then(({ default: ThreeGlobe }) => {
            extend({ ThreeGlobe });
            setIsGlobeReady(true);
        });
    }, []);

    useFrame(() => {
        if (globeRef.current) {
            globeRef.current.rotation.y += 0.003;
        }
    });

    useLayoutEffect(() => {
        if (!globeRef.current) return;
        
        const globeMaterial = globeRef.current.globeMaterial();
        globeMaterial.color = new Color(0x3a228a);
        globeMaterial.emissive = new Color(0x220038);
        globeMaterial.emissiveIntensity = 0.1;
        globeMaterial.shininess = 0.7;

        globeRef.current.showAtmosphere(true);
        globeRef.current.atmosphereColor("#3a228a");
        globeRef.current.atmosphereAltitude(0.25);

        globeRef.current.hexPolygonsData(countries.features);
        globeRef.current.hexPolygonResolution(3);
        globeRef.current.hexPolygonMargin(0.7);
        globeRef.current.hexPolygonColor((e) => {
            if (
                ["KGZ", "KOR", "THA", "RUS", "UZB", "IDN", "KAZ", "MYS"].includes(
                    e.properties.ISO_A3
                )
            ) {
                return "rgba(255,255,255, 1)";
            } else return "rgba(255,255,255, 0.7)";
        });

        globeRef.current.rotation.x = 0.5;
        globeRef.current.rotation.z = 0.3;

        globeRef.current.arcsData(arcsData);
        globeRef.current.arcColor('color');
        globeRef.current.arcDashLength(0.4);
        globeRef.current.arcDashGap(4);
        globeRef.current.arcDashInitialGap(() => Math.random() * 5);
        globeRef.current.arcDashAnimateTime(2000);
    }, [isGlobeReady]);

    if (!isGlobeReady) return <Loading />;

    return (
        <threeGlobe ref={globeRef} />
    );
};

const Globe = () => {
    const canvasWidth = typeof window !== 'undefined' ? window.innerWidth : 800;
    const canvasHeight = typeof window !== 'undefined' ? window.innerHeight : 600;

    return (
        <Canvas
            gl={{ antialias: true, toneMapping: NoToneMapping }}
            size={{ width: canvasWidth, height: canvasHeight }}
            resize={{ scroll: false }}
        >
            <ambientLight intensity={0.01} color={0xbbbbbb} />
            <fog attach={'fog'} color={0x535ef3} near={1} far={2000} />
            <directionalLight color={0xffffff} intensity={0.5} position={[-800, 1500, 300]} />
            <directionalLight color={0x7982f6} intensity={0.8} position={[-300, 400, 50]} />
            <spotLight color={'#802959'} intensity={0.8} position={[50, 500, -40]} angle={0.5} />
            <directionalLight color={'#bff7ff'} intensity={3} position={[-300, 400, -300]} />
            <directionalLight color={'#bff7ff'} intensity={2} position={[-300, 400, -150]} />
            <directionalLight color={'#bff7ff'} intensity={50} position={[-250, 250, -450]} />
            <directionalLight color={'#bff7ff'} intensity={50} position={[-270, 0, -470]} />
            <directionalLight color={'#bff7ff'} intensity={50} position={[0, 250, -500]} />
            <pointLight color={0x8566cc} intensity={0.8} position={[-200, 500, 450]} />
            <CameraPosition />
            <GlobeObj />
        </Canvas>
    );
};

export default Globe;

原错误调用栈

Call Stack
updateDehydratedSuspenseComponent
node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js (16356:0)
updateSuspenseComponent
node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js (16056:0)
beginWork$1
node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js (17369:0)
beginWork
node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js (25672:0)
performUnitOfWork
node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js (24523:0)
workLoopSync
node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js (24239:0)
renderRootSync
node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js (24204:0)
performConcurrentWorkOnRoot
node_modules/next/dist/compiled/react-dom/cjs/react-dom.development.js (23343:0)
workLoop
node_modules/next/dist/compiled/scheduler/cjs/scheduler.development.js (261:0)
flushWork
node_modules/next/dist/compiled/scheduler/cjs/scheduler.development.js (230:0)
MessagePort.performWorkUntilDeadline
node_modules/next/dist/compiled/scheduler/cjs/scheduler.development.js (537:0)

内容的提问来源于stack exchange,提问作者24Chromosones

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 04:45:54