Can not load GLTFLoader in nextjs application!

I’m using react-three-fiber , three and nextjs to display some birds which is a common example in threejs ! Here is my index file of a nextjs app:

import React, { useRef, useState, useEffect } from 'react';
import * as THREE from 'three';
import { Canvas, useFrame, useLoader } from 'react-three-fiber';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';

function Box(props) {
  // This reference will give us direct access to the mesh
  const mesh = useRef();

  // Set up state for the hovered and active state
  const [hovered, setHover] = useState(false);
  const [active, setActive] = useState(false);

  // Rotate mesh every frame, this is outside of React without overhead
  // eslint-disable-next-line no-return-assign,no-multi-assign
  useFrame(() => (mesh.current.rotation.x = mesh.current.rotation.y += 0.01));

  return (
    <mesh
      {...props}
      ref={mesh}
      scale={active ? [1.5, 1.5, 1.5] : [1, 1, 1]}
      onClick={e => setActive(!active)}
      onPointerOver={e => setHover(true)}
      onPointerOut={e => setHover(false)}
    >
      <boxBufferGeometry attach="geometry" args={[1, 1, 1]} />
      <meshStandardMaterial
        attach="material"
        color={hovered ? 'hotpink' : 'orange'}
      />
    </mesh>
  );
}

// This component was auto-generated from GLTF by: https://github.com/react-spring/gltfjsx
function Bird({ speed, factor, url, ...props }) {
  const gltf = useLoader(GLTFLoader, url);
  const group = useRef();
  const [mixer] = useState(() => new THREE.AnimationMixer());
  useEffect(
    () => void mixer.clipAction(gltf.animations[0], group.current).play(),
    [gltf.animations, mixer],
  );
  useFrame((state, delta) => {
    group.current.rotation.y +=
      Math.sin((delta * factor) / 2) * Math.cos((delta * factor) / 2) * 1.5;
    mixer.update(delta * speed);
  });
  return (
    <group ref={group}>
      <scene name="Scene" {...props}>
        <mesh
          name="Object_0"
          morphTargetDictionary={gltf.__$[1].morphTargetDictionary}
          morphTargetInfluences={gltf.__$[1].morphTargetInfluences}
          rotation={[1.5707964611537577, 0, 0]}
        >
          <bufferGeometry attach="geometry" {...gltf.__$[1].geometry} />
          <meshStandardMaterial
            attach="material"
            {...gltf.__$[1].material}
            name="Material_0_COLOR_0"
          />
        </mesh>
      </scene>
    </group>
  );
}

function Birds() {
  return new Array(2).fill().map((_, i) => {
    const x = (15 + Math.random() * 30) * (Math.round(Math.random()) ? -1 : 1);
    const y = -10 + Math.random() * 20;
    const z = -5 + Math.random() * 10;
    const bird = ['stork', 'parrot', 'flamingo'][Math.round(Math.random() * 2)];
    const speed = bird === 'stork' ? 0.5 : bird === 'flamingo' ? 2 : 5;
    const factor =
      bird === 'stork'
        ? 0.5 + Math.random()
        : bird === 'flamingo'
        ? 0.25 + Math.random()
        : 1 + Math.random() - 0.5;
    return (
      <Bird
        key={i}
        position={[x, y, z]}
        rotation={[0, x > 0 ? Math.PI : 0, 0]}
        speed={speed}
        factor={factor}
        url={`/static/glb/${bird}.glb`}
      />
    );
  });
}

const MyComponent = props => {
  return (
    <Canvas>
      <ambientLight />
      <pointLight position={[10, 0, 10]} />
      <Box position={[-1.2, 2, 0]} />
      <Box position={[1.2, 0, 0]} />
      <Box position={[3, -2, 0]} />
      <Birds />
    </Canvas>
  );
};

export default MyComponent;

My files are in static/glb/… folder! The problem is with the GLTFLoader loader. I tried extend method of "react-three-fiber" but not helpful!

I think maybe it’s because of SSR. Do you think so?

What errors are you seeing in the console? Is it failing to load the loader itself, or the model?

1 Like

Whether SSR should work is probably a better question for the react-three-fiber forums, I have no idea. :slightly_smiling_face:

1 Like

/cc

1 Like

The problem solved it was mostly because of the server side rendering in nextjs! I will post the answer here as soon as possible!

Hey,

how did you manage to solve this? :slight_smile:

I’m looking for an answer for this also. The stackoverflow referenced above is misguided.

@donmccurdy

the issue is related to threejs/examples, this has nothing to do with R3F. You could be in a plain javascript app and this line alone will bring SSR down:

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'

GLTFLoader has raw, untranspiled import statements (which are not supported in node, where SSR runs), does not offer a cjs build, and it breaks node resolution. I opened a bug for this on GH once but nothing came out of it. i feel like this will only get worse in time, SSR is very common, Node, Gatsby, Next, etc. the complaints about this will pile up.

the solution would be to make jsm a real package, with a package json, esm + cjs, and internal imports pull from three, not ../.../.../.../three.module.js. this would also fix all the other bugs caused by it, like threejs being broken in cjs environments.

the solution would be to make jsm a real package, with a package json, esm + cjs,

The amount of work required to maintain and publish npm modules for everything in examples/jsm would be extreme. I don’t think that is within our bandwidth. We are in the process of moving entirely to ES modules, which is a large and long-running project already.

internal imports pull from three , not ../.../.../.../three.module.js

This is possible, maybe, without packaging each example as an npm package.

GLTFLoader has raw, untranspiled import statements (which are not supported in node…

Could you explain what you mean here? I’m aware that Node.js’s ES module support is messy, but it’s absolutely possible to get relative ES module imports working in Node.js.


Perhaps more importantly — what are we expecting SSR to do with THREE.WebGLRenderer? The challenges of doing WebGL on the server are (much, much) deeper than just importing a particular module format. I don’t think it’s realistic to expect SSR to work out of the box with three.js.

1 Like

We are in the process of moving entirely to ES modules, which is a large and long-running project already.

that sounds great! i think when it has progressed further packaging this up wouldn’t be much work

without packaging each example as an npm package.

i think this wouldn’t be necessary, just one package “three-examples” would be enough. it could have the bare esm modules and maybe a cjs folder where they are transpiled by rollup

Could you explain what you mean here? I’m aware that Node.js’s ES module support is messy, but it’s absolutely possible to get relative ES module imports working in Node.js.

SSR usually comes in ready-made solutions like Next or Gatsby. You don’t normally have to face node. A package that serves import statements is not expected in the npm eco system. Even if node gets esm, and it looks messy atm, jsm still breaks resolution.

Perhaps more importantly — what are we expecting SSR to do with THREE.WebGLRenderer?

SSR means the server sends a skeleton of the app, which renders quickly, and then gets hydrated on the client. THREE.Xyz has no use there, it just shouldn’t crash - but currently it does.

It isn’t related to SSR, though. Three/examples has a very unusual, and you could say broken, way of publishing its contents. It causes problems in lots of environments, SSR is one of them. This is also the reason why threejs/jsm is broken in Codesandbox or in any CJS env.

I used this:

import React, { Suspense, useEffect, useRef } from 'react'
import { Canvas, useLoader, useThree  } from 'react-three-fiber'

let GLTFLoader

function Model(props) {
  GLTFLoader = require('three/examples/jsm/loaders/GLTFLoader').GLTFLoader
  const group = useRef()
  const gltf = useLoader(GLTFLoader, 'glb/model.glb')
  return (
<group ref={group} {...props} dispose={null}>
  <mesh 
    material={gltf.materials.lambert2SG} 
    geometry={gltf.nodes.Object_3.geometry} 
    scale={[0.03, 0.03, 0.03]} 
    material-color=""/>
  <mesh 
    material={gltf.materials.lambert2SG} 
    geometry={gltf.nodes.Object_4.geometry} 
    material-color="red" />
</group>
  )
}
1 Like

@donmccurdy

Would it be possible to revisit the proposal here on publishing a package like three-examples? Our development environment uses node and webpack, and has the setup where we assume that files in node_modules have been transpiled and thus do not transpile them. As a result, we could not import modules in examples/jsm as they contain untranspiled import statement.

Currently we are considering publishing a package like three-examples mentioned in our private registry as a workaround, but having it be a publicly available package would be great. Thanks!

@Cristianvj 's solution worked for me as well. I called the ‘require’ statement inside an useEffect hook, so the ‘require’ would only be executed client side / in the browser (not SSR). I expect using dynamic import in this manner, would also work (if you wish to avoid using ‘require’).

Hey there @Cristianvj or @stevenjmarsh. If possible, would love to see a Codesandbox of the above solution working. Having trouble getting this solution to work.

This is a bit more than a CodeSandbox, but feel free to take a look at a public repo I have…


A couple source files to pay particular attention to are:
ModelViewer.js - a component that displays the model
tj.js - several utility / convenience functions for Three.js

The ModelViewer component is loaded from pages/index.js.

Note, any next.js setup you see in _app.js or _document.js is not necessary for Three.js or model viewing. The setup in those files is for theming and snackbar user feedback.

In this project Three.js is included as an npm package.

By default, the app will display a model from a hardcoded url of a model I have stored in AWS S3. You can provide a URL to view other models using query params, e.g.: http://localhost:3000/?modelUrl=https://some_other_url/model.glb. Or you could also add a model to the project public folder, and load it from there.

use next dynamic import with SSR set to false:

import dynamic from “next/dynamic”;
const ComponentWithGLTFLoader = dynamic(() => import("./ComponentWithGLTFLoader"), {
ssr: false,
});

this exists now: three-stdlib imports fixed, flatbundled, transpiled, bablified, types, esm and cjs for node.

Hey, I’m facing this exact error. I tried almost everything I could find online. Can you tell me how you overcame this issue?

use three-stdlib as linked to in the post before yours. this has imports/exports that node understands. if you use useGLTF from drei https://github.com/pmndrs/drei#usegltf then this will all work ootb.