我知道这个特定错误已被多次询问,但我认为这种情况足够独特,值得单独发表。
所以我刚刚开始思考 nextjs 14 中的 Three.js。 一切似乎都在本地完美运行,但我仍然看到以下错误: ReferenceError:文档未定义
我认为这里有一个点,我想早期使用文档来创建画布,但我还没有找到它,因为我想它将在 Threejs 代码本身中。
这里有人做过类似的事情并且知道这个问题在哪里触发吗?
"use client";
export const dynamic = "auto";
import { ISSPosition, fetchISSPosition } from "@/lib/api";
import { convertCoordsToSphere } from "@/lib/functions";
import React, { useRef, useEffect, useState } from "react";
import * as THREE from "three";
const earthRadius = 10;
const scene = new THREE.Scene();
// Fog setup
...
// Camera and renderer setup
...
const renderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true,
});
// Lighting setup
...
// Earth model setup
const geometry = new THREE.SphereGeometry(earthRadius, 64, 64);
const earthMat = new THREE.MeshPhongMaterial({
shininess: 350,
map: new THREE.TextureLoader().load("/earth_texture.jpg"),
bumpMap: new THREE.TextureLoader().load("/earth_texture.jpg"),
});
const earth = new THREE.Mesh(geometry, earthMat);
earth.receiveShadow = true;
earth.rotation.x = 0.3;
scene.add(earth); // Add Earth to the scene upfront
// ISS model setup
...
const animate = () => {
requestAnimationFrame(animate);
earth.rotation.y -= 0.001;
renderer?.render(scene, camera);
};
const ThreeScene = () => {
const [issPosition, setIssPosition] = useState<ISSPosition>();
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const fetchISSData = async () => {
setIssPosition(await fetchISSPosition());
};
const intervalId = setInterval(fetchISSData, 5000);
if (typeof window !== "undefined") {
containerRef.current?.appendChild(renderer.domElement);
}
animate();
const handleResize = () => {
if (renderer) {
let size =
window.innerWidth * 0.8 > 800 ? 800 : window.innerWidth * 0.8;
renderer.setSize(size, size);
}
};
handleResize();
window.addEventListener("resize", handleResize);
return () => {
clearInterval(intervalId);
};
}, []);
useEffect(() => {
const animate = () => {
requestAnimationFrame(animate);
if (issPosition) {
const pointCoords = convertCoordsToSphere(
Number(issPosition.iss_position.latitude),
-Number(issPosition.iss_position.longitude),
earthRadius
);
issPoint.position.copy(pointCoords.applyMatrix4(earth.matrix));
scene.add(issPoint);
}
renderer.render(scene, camera);
};
animate();
}, [issPosition]);
return (
typeof window !== "undefined" && (
<>
<div className="m-auto max-w-fit" ref={containerRef}></div>
{issPosition ? (
<div className="flex flex-col gap-2 mb-8 w-fit justify-center items-center mx-auto">
<span>Latitude: {issPosition?.iss_position.latitude}</span>
<span>Longitude: {issPosition?.iss_position.longitude}</span>
</div>
) : (
<div className="flex gap-4 justify-center">
<p className="mb-4">Locating ISS</p>
<i className="border-b- border-primary w-4 h-4 rounded-full animate-spin"></i>
</div>
)}
</>
)
);
};
export default ThreeScene;
这个问题的原因是,
Next js 默认执行服务端渲染。这意味着您的 JavaScript 代码先在服务器上执行,然后再发送到客户端的浏览器。由于
document
对象是浏览器 API 的一部分,并且在服务器上不存在,因此当您的代码尝试在 SSR 期间访问它时,您会遇到此错误。
即使认为在本地环境中似乎没问题,因为您执行
npm run dev
,该应用程序将无法在生产环境中运行。尝试运行 npm build
,然后它会显示此错误并希望完成构建。因此,要构建,您需要解决问题。
您遇到的
document
错误可能源于 Three.js 场景的初始化,尤其是渲染器,因为 THREE.WebGLRenderer()
使用 document
创建 <canvas>
元素。由于这发生在任何客户端检查或生命周期方法之外,因此它在服务器端上下文中进行评估,其中 document
未定义。
要解决此问题,您应该确保使用
document
或其他特定于浏览器的 API 的任何代码仅在客户端执行。在 Next.js 中,这通常是通过使用 if (typeof window !== "undefined") { ... }
之类的检查来保护此类代码来处理的,您已经对部分代码进行了检查。
但是,渲染器是在任何组件生命周期方法或客户端检查之外创建的,从而导致 SSR 问题。
我修改了你的代码,
const ThreeScene = () => {
const [issPosition, setIssPosition] = useState<ISSPosition>();
const containerRef = useRef<HTMLDivElement>(null);
const [renderer, setRenderer] = useState<THREE.WebGLRenderer | null>(null);
useEffect(() => {
// Ensure this runs only on the client side
if (typeof window !== "undefined" && !renderer) {
const newRenderer = new THREE.WebGLRenderer({
alpha: true,
antialias: true,
});
setRenderer(newRenderer);
containerRef.current?.appendChild(newRenderer.domElement);
}
}, [renderer]);
useEffect(() => {
const fetchISSData = async () => {
setIssPosition(await fetchISSPosition());
};
const intervalId = setInterval(fetchISSData, 5000);
// Move the animation code inside this useEffect
if (renderer && typeof window !== "undefined") {
const animate = () => {
requestAnimationFrame(animate);
earth.rotation.y -= 0.001;
renderer.render(scene, camera);
};
animate();
const handleResize = () => {
let size = window.innerWidth * 0.8 > 800 ? 800 : window.innerWidth * 0.8;
renderer.setSize(size, size);
};
handleResize();
window.addEventListener("resize", handleResize);
}
return () => {
clearInterval(intervalId);
};
}, [renderer, issPosition]); // Added renderer to dependency array
// The rest of your component...
};