Getting a production ready setup using the 3D framework Babylon.js and Next.js server side rendering may appear simple at the first glance. However, even seasoned frontend developers may find this task to be time consuming, difficult and confusing.
This is because of the way Next.js handles code compilation in the backend, while serving static files to the frontend, using the same source code. And it gets even harder to debug when you bump into nasty Webpack npm compilation issues.
To ease that pain, this guide provides a working starter template (used by 3Designer itself) that you can kickstart any Babylon project, in no time! But most importantly, it comes packed with these optimizations:
- SEO Friendly
- Load Fast to achieve “A” Grade Page Speed score
- Async Load Babylon to improve UX (user experience)
- Code Splitting with Tree Shaking to reduce bundle size
- An option to write in pure Javascript or Typescript (or mixing both)
- Intuitive developer experience with HMR (Hot Module Reload)
- Jest Test working setup
- Modular Architecture for a flexible, maintainable and scalable app
- Production ready with detailed bundle analyzer.
Making 3D Scenes SEO Friendly
As you may know, Google and major search engines are really good at indexing static HTML pages. However, at the moment of writing, they still struggle with dynamic content generated in the frontend using Javascript. This is because most index crawlers (search engine bots) will not wait until your 3D assets and scripts fully load.
A little trick that keeps both search engines and humans happy, is to get Next.js to render the HTML page initially on server side with only critical information about the 3D scene:
- Title (also used as Meta Title)
- Brief text description
- Preview snapshot of the 3D Scene (generated automatically by Babylon).
Example preview image shown to user while loading Babylon.js code:
In this example, we reduced the actual fully loaded time of 5.1 seconds to the perceived 2.2 seconds. That’s when the preview image fully loaded.
Speed Visualization
This way, time to the First Contentful Paint can be incredibly fast (under 1 second). Search engines can understand what your page is about. Human visitors are happy because they can see the project preview almost immediately while waiting for the assets to load.
“A” Grade Page Speed Score Life Hack
Previously, we took care of SEO with SSR (server side rendering). Now, it’s times for some tweaks to make sure we have a good page score from speed graders, such as GTmetrix.
- Most real world use cases of Three.js or Babylon involve some kind of 3D product configurator, portfolio 3D viewer or 3D render visualization. And none of these scenarios requires it to be fully loaded on all pages of your website.
- Even if a page has a 3D scene view, it does not mean you have to load it immediately on initial page load, thus ruining its Page Speed core. Perhaps, the 3D view is buried somewhere down the second or third page fold.
In any of these cases, you can achieve an “A” Grade Page Score by deferring the loading of Babylon.js (or Three.js) bundle. Load on demand, while providing great user experience with preloading.
Here is the GTmetrix proof – this page does not have any 3D code, but it list 3D projects and preloads Babylon bundle, so that upon clicking it will load the 3D scene (for the purpose of testing, all projects were remove to avoid preview images ruining the page score):
Preload Large Assets for Smooth UI
Preloading works by asynchronously loading all the scripts and static assets required to render a 3D scene upfront, when the user is about to reach the 3D configurator on your page. And when the user actually reaches the 3D view, s/he doesn’t have to wait for Babylon scripts to load. It is ready for a smooth VR experience!
Fortunately for us, Next.js provides a built-in way of preloading all static assets for an entire page (i.e. route). We can use it to preload Babylon assets, either on the same page, or before the user is about to navigate to the page with 3D configurator.
// Preload.js
import NextLink from 'next/link'
/**
* Preload Script for given Route without rendering (only works in Next.js production)
* @param {String} to - pathname of the route to preload scripts for
* @param {Object} [props] - other next Link options
*/
export default function Preload ({to, ...props}) {
// Next Link expects a native element with ref, and it has to render as HTML element to trigger preload
return <NextLink href={to} {...props}><i style={style}/></NextLink>
}
const style = {
position: 'absolute',
width: 0,
height: 0,
}
All you have to do, is create a dummy Next.js page that imports all Babylon codes. Then preload it when needed using the Preload React Component. It will load scripts when the <Preload/> component gets into view.
Async Load 3D Code to Improve UX
In your project, make sure any entry point in one of Next.js pages that has Babylon code uses Next’s dynamic import, like so:
// pages/scene/[id].js
const SceneView = dynamic(() => import(`../../../client/views/SceneView`), {ssr: false})
export default ClientPage(SceneView)
/**
* Create Client Only Renderer for given Component
*/
function ClientPage (Component, options, loader) {
return function Page ({loaded = null, ...props}) {
const [ready, setReady] = useState(false)
return <>
{loaded && loader && <LoadingProgressBar value={ready ? 1 : 0}/>}
{loaded && <Component {...options} onReady={setReady} {...props}/>}
</>
}
}
Note the loaded
prop, set it to true in client side in browser, after the page successfully loads with static SSR content from the previous step. The best place to set this loaded
flag is inside _app.js, using React’s ComponentDidMount lifecycle.
This tells Next.js to skip loading the entire Babylon library on server side. It results in much less stress on your server and much faster response to the users, since 3D Babylon code is only needed in client side any way.
That’s it! You can grab this minimum setup – Babylon + Next.js production ready boilerplate on GitHub.
Good Luck!