pmndrs / react-three-fiber

🇨🇭 A React renderer for Three.js
https://docs.pmnd.rs/react-three-fiber
MIT License
27.62k stars 1.6k forks source link

ReferenceError: document is not defined (with useLoader) #3357

Closed sovetski closed 2 months ago

sovetski commented 2 months ago

When I run npm run build, I get this error on a nextjs application: "ReferenceError: document is not defined" so the build is cancelled due to this error

My code:

"use client";

import { Canvas, useLoader } from "@react-three/fiber";
import * as THREE from "three";

export default function Page() {

    const backgroundImage = useLoader(THREE.TextureLoader, "/bg.png");

    return (
        <Canvas scene={{ background: backgroundImage }}>
        {/* blabla */}
        </Canvas>
    );
}

When I remove this part the build works correctly

const backgroundImage = useLoader(THREE.TextureLoader, "/bg.png");

My dependencies:

    "dependencies": {
        "@react-three/drei": "^9.112.0",
        "@react-three/fiber": "^8.17.7",
        "@types/three": "^0.168.0",
        "next": "~14.2.11",
        "react": "^18.3.1",
        "react-dom": "^18.3.1",
        "three": "^0.168.0"
    },
sovetski commented 2 months ago

As a temporary solution I am using it with try catch, build process pass but it does not work correctly, any idea on what is the problem?

    let backgroundImage = null;

    try {
        backgroundImage = useLoader(THREE.TextureLoader, "/bg.png");
    } catch (error) {}

For information, when I use it with Suspense like explained in the documentation here, I get the Uncaught Error: document is not defined even without build process

EDIT:

So this version looks working:

    let backgroundImage = null;

    try {
        backgroundImage = new THREE.TextureLoader().load("/bg.png");
    } catch (error) {}

Something is wrong with useLoader? I don't really like the try catch but as a temporary solution I will keep this for the moment

CodyJasonBennett commented 2 months ago

TextureLoader is requiring browser APIs that aren't available on the server (client components are still server-prerendered). I would advise you to disable SSR for the Canvas with next/dynamic. https://nextjs.org/docs/pages/building-your-application/optimizing/lazy-loading#with-no-ssr

As an additional note, try to keep hooks within the Canvas, so instead:

// MyCanvas.jsx
import { Canvas, useLoader } from "@react-three/fiber";
import * as THREE from "three";

function Background() {

    const texture = useLoader(THREE.TextureLoader, "/bg.png");
    return <primitive object={texture} attach="background" />; // either this or write scene.background

}

export default function Page() {

    return (
        <Canvas>
            <Background />
        {/* blabla */}
        </Canvas>
    );
}
'use client';

import dynamic from 'next/dynamic';

const Page = dynamic(() => import('./Page'), { ssr: false });