2

How to load a model GLTF model in next.js?

I spend hours looking for this. Nothing works :(


What I tried so far:

  • use different loaders ( useLoader(GLTFLoader,url) / useGLTF(url) and some more
  • wrap the component in a next/dynamic component / dont do it
  • solve the errors related to suspense not beeing support by installing next with react 18
  • tried this starter template
  • use three-stdlib
  • tried to write a custom loader in next.config.js

read every issue and forum post i could find on tis issue


The error i get at the moment is:

Server Error
Error: Could not load  <url> response.body.getReader is not a function

with a component looking like this:

import React from 'react'
import { useGLTF } from '@react-three/drei'
import { Canvas, } from '@react-three/fiber'
import { Suspense } from 'react/cjs/react.production.min';

export default function Spinner({ ...props }) {
  const model = useGLTF("http://localhost:3000/spinner.glb")
  return (
    <Suspense fallback={"loading"}>
      <Canvas
        camera={{ position: [1, 1, 1] }}
      >
        <primitive object={model.scene} />
        <color attach="background" args={["hotpink"]} />
      </Canvas>
    </Suspense>
  )
}

package.json:

  },
  "dependencies": {
    "@react-three/drei": "^7.27.3",
    "@react-three/fiber": "^7.0.21",
    "axios": "^0.24.0",
    "next": "^12.0.7",
    "react": "^18.0.0-beta-24dd07bd2-20211208",
    "react-dom": "^18.0.0-beta-24dd07bd2-20211208",
    "three": "^0.135.0",
    "three-stdlib": "^2.6.1"
  },
  "devDependencies": {
    "eslint": "8.4.1",
    "eslint-config-next": "12.0.7",
    "file-loader": "^6.2.0"
  }
}

node-version:

16 LTS
Alex
  • 111
  • 2
  • 9

3 Answers3

6

Wrapping your Model component with the parent and using lazy import solves the issue, e.g.

Model component

import React from 'react'
import { useGLTF } from '@react-three/drei'

export default function Model() {
  const model = useGLTF("http://localhost:3000/spinner.glb")
  return (
     <primitive object={model.scene} />
  )
}

Scene component with lazy() import

import { lazy, Suspense } from 'react'
import { Canvas, } from '@react-three/fiber'

const ModelComponent = lazy(() => import("./model"));

export default function Spinner({ ...props }) {
  return (
    <Suspense fallback={"loading"}>
      <Canvas
        camera={{ position: [1, 1, 1] }}
      >
        <ModelComponent />
        <color attach="background" args={["hotpink"]} />
      </Canvas>
    </Suspense>
  )
}

This seems to be related to SSR. Similar problems are with TextureLoaders in Next and was having similar hard time to fix it and eventually found that solution with lazy() import. I had just tried that for the model load and it works fine. Can't track this original thread right now, but will try to track and add it here.

grzehu
  • 106
  • 3
  • 2
    Yee for me it was enough to import split the component so that the component with the loader was inside the canvas and suspense. I think then the loader is also wrapped in the suspense component and behave fine - no stress with ssr. I wonder why dynamic import wasn't enough – Alex Dec 22 '21 at 01:57
0

In nextJS you don't need to use the suspense component. Use the useTexture hook from @react-three/drei instead of loading using useLoader.

This example code loads the model with texture.

import React from "react";
import { useTexture } from "@react-three/drei";

function Box() {
    const colorMap = useTexture("/img/robot.png");
    return (
        <mesh rotation={[90, 0, 20]}>
            <boxBufferGeometry attach="geometry" args={[2, 2, 2]} />
            <meshNormalMaterial attach="material" />
        </mesh>
    );
}

export default Box;
0

What worked in my case is :

import React from 'react'
import { useGLTF } from '@react-three/drei'

import Spinner from "@/public/spinner.glb"

export default function Model () {
    const glb = useGLTF(Spinner.src)
    return (
            <primitive object={model.scene} />
    )
}