1

I had a working 3d model viewer in react, which i am now trying to port to Next. I need GLTFLoader and OrbitControls which are giving me the problem in react I loaded this like:

import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

can't do that because Ill get this error in next:

SyntaxError: Cannot use import statement outside a module
This error happened while generating the page. Any console logs will be displayed in the terminal window.
Source

.next\server\pages\index.js (1:0) @ Object.three/examples/jsm/loaders/GLTFLoader

> 1 | module.exports = require("three/examples/jsm/loaders/GLTFLoader");

Then I tried to use the three-std library and import the controls and loader from there. Same error. I then tried to use the require('') to import it but I got the same error again. I found some almost similar problems on google, but no solution that was easy to understand.

full code:

import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
// import Model3d from '../assets/centered.gltf'
// import Model3d from '../assets/3dmodel.gltf'
import D3d from '../assets/images/3d.svg'

import React from 'react'

// let GLTFLoader
// let OrbitControls

const VisA = () => {
  // GLTFLoader = require('three/examples/jsm/loaders/GLTFLoader').GLTFLoader
  // OrbitControls = require('three/examples/js/controls/OrbitControls')
  //   .OrbitControls

  const { useRef, useEffect } = React
  const mount = useRef(null)
  const hitbox = useRef(null)
  const controls = useRef(null)

  useEffect(() => {
    let width = mount.current.clientWidth
    let height = mount.current.clientHeight
    let frameId

    const scene = new THREE.Scene()
    const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000)
    const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
    const loader = new GLTFLoader()
    const camcontrols = new OrbitControls(camera, hitbox.current)


    // loader.load(Model3d, function (gltf) {
    loader.load('./3dmodel/3dmodel.gltf', function (gltf) {
      gltf.scene.scale.set(14, 14, 14)
      var box = new THREE.Box3().setFromObject(gltf.scene)
      var center = new THREE.Vector3()
      box.getCenter(center)
      gltf.scene.position.sub(center)
      scene.add(gltf.scene)
    })

    const hemiLight = new THREE.HemisphereLight(0xffeeb1, 0x80820, 20)
    const light2 = new THREE.DirectionalLight(0xfdeee1, 20)
    const spotLight = new THREE.SpotLight(0xfdeee1, 4)
    const spotLight2 = new THREE.SpotLight(0xfdeee1, 4)
    scene.add(hemiLight)
    scene.add(spotLight)
    scene.add(spotLight2)
    light2.position.set(10, -50, 500)
    scene.add(light2)
    scene.rotation.y = -1.65
    camera.position.z = 4
    // scene.add(cube)
    // renderer.setClearColor('#FFFFFF')
    renderer.setSize(width, height)

    const renderScene = () => {
      renderer.render(scene, camera)
    }

    const handleResize = () => {
      width = mount.current.clientWidth
      height = mount.current.clientHeight
      renderer.setSize(width, height)
      camera.aspect = width / height
      camera.updateProjectionMatrix()
      renderScene()
    }

    const animate = () => {
      spotLight.position.set(
        camera.position.x + 10,
        camera.position.y + 10,
        camera.position.z + 10
      )
      spotLight2.position.set(
        camera.position.x - 10,
        camera.position.y - 10,
        camera.position.z - 10
      )

      renderScene()
      frameId = window.requestAnimationFrame(animate)
    }

    const start = () => {
      if (!frameId) {
        frameId = requestAnimationFrame(animate)
      }
    }

    const stop = () => {
      cancelAnimationFrame(frameId)
      frameId = null
    }

    mount.current.appendChild(renderer.domElement)
    window.addEventListener('resize', handleResize)
    start()

    controls.current = { start, stop }


    return () => {
      stop()
      window.removeEventListener('resize', handleResize)
      mount.current.removeChild(renderer.domElement)


    }
  }, [])

  return (
    <div
      className="relative flex items-center justify-center w-full h-full "
      ref={mount}
      // onClick={() => setAnimating(!isAnimating)}
    >
      <div className="absolute w-full h-full">
        <D3d className="w-10 h-10 mt-10 text-gray-800 fill-current " />
      </div>
      <div className="absolute w-3/5 h-4/5" ref={hitbox}></div>
    </div>
  )
}
export default VisA

2 Answers 2

4

The Problem

You're importing modules written in ESM format and when NextJS attempts to server-side render your code node doesn't understand ESM -- Node.js expects CJS (CommonJS) typically.

The Solution

It depends.

1. Do you want your code using three to run server-side?

(this assumes three can run in node, which I can't say one way or the other)

I think there are a couple of options here:

a) Use a version of three compatible with Node.js and the Browser. Glancing over this issue, you may consider trying three-universal (I have not used it).

b) Customize your Webpack behavior to bundle and transpile the three code into CJS for the server. Personally, I don't recommend this as a first step. Customizing Webpack can become very complicated very quickly, and doing so in NextJS comes with its own added complexities.

2. Modify your code using three to run only client-side.

Use a dynamic import. From that reference:

You may not always want to include a module on server-side. For example, when the module includes a library that only works in the browser.

Additional Feedback

Please note, the answer above is conceptual. If you are able to provide a reproduction by way of a repository or otherwise I may be able to give a more concrete solution.

Sign up to request clarification or add additional context in comments.

2 Comments

Thank you very much for your constructive answer, you don't know how much I apppreciate it. I willl have a look at dynamic imports, this seems the easiest and makes most sense.
I got it working... your comment helped me out immensely, it is really hard to wade trough half information as a newbie. The solution was surprisingly easy.
0

With help of Mcy, i found that in my case adding a dynamic import would be easiest. And it was pretty easy, I didn't change anything in the viewer component I had from react, and loaded the component on the pages/index.js with:

import dynamic from 'next/dynamic' 
const Dynamic3dViewr = dynamic(() => import('../components/Viewer3d.js'), {
  ssr: false,
})

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.