How to use Babylon with Next JS

Hi im new to the forum. Could someone help me with a step by step guide on how to use Babylon with Nextjs. I just need a simple example/tutorial beginning with what to install and how to write the code into a Nextjs index.js file so i can render it on the browser. Thanks to everyone in advance

2 Likes

No sure we have a tuto for it but I think @RaananW is pretty familiar with Next and might have pointers

Iā€™ve used Babylon with NextJS. This is an example component Iā€™ve used:

import { Engine, Scene } from "babylonjs";
import React, { useEffect, useRef, useState } from "react";

const BabylonScene = (props) => {
  const reactCanvas = useRef(null);
  const {
    antialias,
    engineOptions,
    adaptToDeviceRatio,
    sceneOptions,
    onRender,
    onSceneReady,
    ...rest
  } = props;

  const [loaded, setLoaded] = useState(false);
  const [scene, setScene] = useState(null);

  useEffect(() => {
    if (window) {
      const resize = () => {
        if (scene) {
          scene.getEngine().resize();
        }
      };
      window.addEventListener("resize", resize);

      return () => {
        window.removeEventListener("resize", resize);
      };
    }
  }, [scene]);

  useEffect(() => {
    if (!loaded) {
      setLoaded(true);
      const engine = new Engine(
        reactCanvas.current,
        antialias,
        engineOptions,
        adaptToDeviceRatio
      );
      const scene = new Scene(engine, sceneOptions);
      setScene(scene);
      if (scene.isReady()) {
        props.onSceneReady(scene);
      } else {
        scene.onReadyObservable.addOnce((scene) => props.onSceneReady(scene));
      }

      engine.runRenderLoop(() => {
        if (typeof onRender === "function") {
          onRender(scene);
        }
        scene.render();
      });
    }

    return () => {
      if (scene !== null) {
        scene.dispose();
      }
    };
  }, [reactCanvas]);

  return (
    <canvas
      style={{ width: "100%", height: "100%" }}
      ref={reactCanvas}
      {...rest}
    />
  );
};

export default BabylonScene;

Here is how I used it with a custom imported model:

import React from "react";
import { SceneLoader } from "babylonjs";
import "babylonjs-loaders";
import BabylonScene from "./BabylonScene";

const onSceneReady = (scene) => {
  console.log("running");
  scene.createDefaultCamera(true);
  SceneLoader.Append(
    "path to 3d model/",
    "Cafe.glb",
    scene,
    (meshes) => {
      scene.activeCamera = meshes.cameras[1];
    }
  );

  return scene;
};

const onRender = (scene) => {};

const CafeBanner = () => {
  return (
    <BabylonScene
      antialias
      onSceneReady={onSceneReady}
      onRender={onRender}
      id="my-canvas"
    />
  );
};

export default CafeBanner;

I believe when I created this I was following this in the documentation: Babylon.js and React | Babylon.js Documentation

3 Likes

The code @jgonzosan offered seems like a perfect starting point.
As Next.js is technically a static react generator, you might also be able to use @brianzinnā€™s babylon-react component, but I havenā€™t tried it yet, so I canā€™t offer any advice on that.

I updated my original response as it was only half of how I used it. The BabylonScene component is essentially a ā€œtemplateā€ that can be used as the basis for a more customized component. The code I posted includes a 3d scene of a cafe I imported.

hi @John_Carlisle (and thanks @RaananW for the mention).

I have a sample repo here:

If you donā€™t want to use the react renderer and prefer imperative as posted above then have a look at the babylonjs-hook branch. You are going to have better tree-shaking results right now until I get a v4 out on react-babylonjs with dynamic registration.

Definitely have a look at the next.config.js require on next-transpile-modules - you can actually remove the withTM parth completely and go like this:

import dynamic from 'next/dynamic'

then in page/index.tsx change

import Wave from '../components/elements/Wave/Wave'

to

const Wave = dynamic(() => import('../components/elements/Wave/Wave'), {
  ssr: false
})

That repo is a year old, but would appreciate if you have any updates and would accept any PRs to bring up-to-date.

The bundle analyzer is on for that project - have a look at the output if you want to optimize the output. Make sure all of your imports are explicit to file. ie: donā€™t import from @babylonjs/core, but @babylonjs/core/fullPathHere

cheers.

2 Likes

With Next.js you should only dynamically import Babylon.js because some packages may cause error when loaded in Node.js.

Next.js uses Node.js on the server side, it will load your code twice, in Node.js first, and then in browser, but the code will only run in browser.

Even if you do not have packages that may break in Node.js, it will still be slower to serve the static pages, than if you skipped loading Babylon from backend.

Here is what I use for any React component that imports anything from Babylon:

Next.js page file

/* next/pages/index.js */
import { ClientOnly, dynamicImportOptions } from 'client/utils/next'
import dynamic from 'next/dynamic'

// This is the component that imports something from Babylon.js
const BabylonComponent= dynamic(() => import(`../BabylonComponent`), dynamicImportOptions)

export default function HomePage (props) {
  return <ClientOnly {...props}>{BabylonComponent}</ClientOnly>
}

Utility file

/* client/utils/next */
import React, { useEffect, useState } from 'react'
import Loading from 'Loading' // Your custom Loading placeholder component (i.e. show spinner)

// @Note: next.js dynamic import only works for React Components,
// and only starts loading when the Component gets called
export const dynamicImportOptions = {ssr: false, loading: () => <Loading loading/>}

/**
 * HOC Wrapper to Render given React Function Component on client side only
 */
export function ClientOnly ({children: Component, ...props}) {
  const [mounted, setMounted] = useState(false)
  useEffect(() => setMounted(true))
  return mounted && <Component {...props}/>
}

I might have some time later to create github template (right now stapped for time), if people want this.

1 Like

Hi everyone, i really appreciate all the replies. Ive had a go at using all the suggested ways to solve it (just seem to be having some challenges with the transpile issue; not being able to resolve or import babylon-loaders and recieving errors for next/dynamic options/requiring object literal). If anyone comes across a really basic step by step way to do it that is guarenteed to work(including what to install, preferrably no need to modify config files, and then exactly what code to paste in each nextjs page), that would be so greatly appreciated. Thanks again for all the help so far

That means Next.js tries to import Babylon in Node.js environment, or you have errors in your setup not related to Babylon.

My code above was copy pasted from existing working project, however, right now I do not have time to setup a template repo to share with you.

You can try my next.config.js:

// @Note: next.config.js gets loaded once at the start, then babel.config.js (loaded repeatedly)
const {withPlugins, optional} = require('next-compose-plugins')
const {PHASE_PRODUCTION_SERVER} = require('next/constants')
let {modulesToTranspile} = require('./config')
modulesToTranspile = modulesToTranspile.filter(v => v !== '@babylonjs')
const clientEnvs = {}
for (const key in process.env) {
  if (key.indexOf('REACT_APP_') === 0) clientEnvs[key] = process.env[key]
}
/** Tested with Next.js v12.22.1 Webpack v4.4.1 */
module.exports = withPlugins(
  /** IMPORTANT: the order of plugins matter! */
  [
    /* Must be the first plugin (to work with decorator {legacy: true}) */
    [optional(() => require('@next/bundle-analyzer')({
      enabled: process.env.ANALYZE === 'true',
    })({})), {}, ['!', PHASE_PRODUCTION_SERVER]],

    /* Webpack configuration must go here */
    [optional(() => require('next-transpile-modules')(modulesToTranspile)), {
      /**
       * The webpack function is executed twice, once for the server and once for the client.
       * @see https://nextjs.org/docs/api-reference/next.config.js/custom-webpack-config
       */
      webpack: (config, {buildId, dev, isServer, defaultLoaders, webpack}) => {
        // Comment this out if you do not use GraphQL
        config.module.rules.push({
          test: /\.(graphql|gql)$/,
          loader: 'graphql-tag/loader', // works with fragment #import
        })
        return config
      },
    }, ['!', PHASE_PRODUCTION_SERVER]],
  ],

  /** next.config.js configuration */
  {
    // @note: 'next-compose-plugins' has a bug and does not call webpack(config) here
    // @see: https://github.com/cyrilwanner/next-compose-plugins/issues/41
    /**
     * For Security reasons, Next.js does not export process.env to client side.
     * Manually export all envs starting with `REACT_APP_*` to sync with CRA best practices
     */
    publicRuntimeConfig: {
      NODE_ENV: process.env.NODE_ENV,
      ...clientEnvs,
    },
    /**
     * @see: https://nextjs.org/docs/advanced-features/i18n-routing
     */
    i18n: {
      locales: ['en', 'fr', 'ru'],
      defaultLocale: 'en',
    },
    /**
     * @see: https://nextjs.org/docs/api-reference/next.config.js/rewrites
     */
    async rewrites () {
      return {
        // These rewrites are checked after both pages/public files
        // and dynamic routes are checked
        fallback: [
          { // Rewrite everything else to use `pages/spa.js` Single Page Application
            source: '/:path*',
            destination: '/spa',
          },
        ]
      }
    },
  }
)

babel.config.js

const {modulesToTranspile} = require('./config')
module.exports = (api) => {
  api.cache(false) // set cache as true/false

  return {
    plugins: [
      [
        '@babel/plugin-proposal-decorators',
        {
          'legacy': true
        }
      ],
      'lodash',
    ],
    presets: [
      'next/babel'
    ],
    ignore: [
      // this duplicated declaration from next.config.js produces slightly smaller bundle
      new RegExp(`node_modules/(?!(${modulesToTranspile.join('|')})/)`)
    ],
  }
}

config.js

module.exports = {
  modulesToTranspile: [
    'validator', // importing ES6 code for tree shaking
    /**
     * frontend only
     * next@10.2.3 with next-transpile-modules@7.3.0 must have this disabled
     * use dynamic import instead to avoid rendering Babylon server side and slowing down dev
     */
    '@babylonjs', // required for Jest test
  ]
}

package.json

  "dependencies": {
    "next": "10.2.3"
  },
  "peerDependencies": {
    "react": "x",
  },
  "devDependencies": {
    "@babel/plugin-proposal-decorators": "^7.10.1",
    "@next/bundle-analyzer": "10.2.3",
    "next-compose-plugins": "^2.2.1",
    "next-transpile-modules": "7.3.0",
    "@testing-library/jest-dom": "^5.11.0",
    "@testing-library/react": "^10.4.3",
    "babel-jest": "^26.1.0",
    "babel-plugin-import-graphql": "^2.8.1",
    "babel-plugin-lodash": "^3.3.4",
    "concurrently": "^5.2.0",
    "identity-obj-proxy": "^3.0.0",
    "jest": "^26.1.0",
    "jest-transform-graphql": "^2.1.0",
    "madge": "^3.9.2",
    "react-test-renderer": "^16.13.1",
    "rimraf": "^3.0.2"
  }

Just my two cents here, and not sure it will help in any way, but donā€™t use babylonjs-loaders, instead use the es version at @babylonjs/loaders . This might work a little better.

For nextjs v11 and below

because next is a cjs node server. If you use the esm version of babylon, you will just have to transpile it to cjs. Although, i think they added esm support in the latest version. They also added swc as the default compiler but will revert to babel if u have a babel config in your project.

The core issue is that next skips transpiling node modules, because practically all packages ship cjs and esm or umd. For packages that dont, like babylon, most people use the ā€œnext-transpile-modulesā€ plugin which just tells next to transpile certain packages inside of node modules.

Here is an example
.next-babylonjs/next.config.js at master Ā· Flux159/next-babylonjs Ā· GitHub


@RaananW I was wrong, updated correction:

For nextjs v12 and above

I just confirmed with the example repo above you can remove next-transpile-modules with nextjs v12, using either babel or swc. So you can use @babylon/core and babel-plugin-glsl (glslify in a babel macro) . w00t.

3 Likes

Hi @John_Carlisle just checking in, howā€™s your project going? Can we help you in anything else? :slight_smile:

Hi carolhmj thanks for checking in. Im still struggling to get babylon running on next js. Ive been continuing my project with just babylon, html and express server until i can solve how to get it running on next js(which is what i really want to be able to do). Any advice you have would be really appreciated. Thanks again John

Here is the the ready made template forum announcement - post questions here.
Direct link to guide: How to Setup Babylon + Next.js in Production Optimized for SEO & Speed

2 Likes

Hi ecoin, im just comming back to this. Do you know if there is a simpler step by step set of instructions i can follow (the template is still a bit to advanced for me).

I am really keen to learn how to use babylon with next js

Thanks again John

For the this here are the step by step (Iā€™m assuming you have Git installed on your Mac computer with terminal access:

git clone https://github.com/ecoinomist/babylon-next.git your-project-name
cd your-project-name
yarn
yarn dev

Then you can begin modifying Babylon scene in this file.

cd repos/client/views
# open the SceneView.js to start editing

Does this answer your question?

that is almost perfect thanks ecoin. The final little thing i am just trying to solve is that when i cd into the project directory and type npm run dev i get an error message(see below). Can you see what i might need to do differently to run it thanks again for all your help John

You need to install yarn package manager first:

  1. Install brew
  2. Install yarn
brew install yarn

Please check out this comment for full instructions: Babylon + Next.js Setup Optimized for SEO, Load Speed & Developer Experience - #24 by ecoin

I think without yarn at least newer versions of 7 and higher of NPM:
ā€˜npm run dev -w webā€™

-w means to run in that workspace

not having much luck (see errors below)

I wish using babylon with next was as simple as just importing a static file/route

Thx