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对象。具体实现:
- 移除全局的ThreeGlobe导入和extend调用
- 在客户端组件内,通过动态导入加载ThreeGlobe,并在useEffect中完成extend操作,确保仅在浏览器环境执行
- 利用状态标记控制组件渲染时机,避免未初始化时渲染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
相关产品推荐
相关产品推荐

