├── .prettierrc
├── README.md
├── next14-app-r3f8-react18
├── .dockerignore
├── .gitignore
├── Dockerfile
├── app
│ ├── layout.js
│ └── page.js
├── jsconfig.json
├── next.config.mjs
├── package-lock.json
└── package.json
├── next14-pages-r3f8-react18
├── .dockerignore
├── .gitignore
├── Dockerfile
├── jsconfig.json
├── next.config.mjs
├── package-lock.json
├── package.json
└── pages
│ └── index.js
├── next15-app-r3f9-react19-rsc
├── .dockerignore
├── .gitignore
├── Dockerfile
├── app
│ ├── ClientBox.js
│ ├── ClientCanvas.js
│ ├── ClientOrbitControls.js
│ ├── layout.js
│ └── page.js
├── jsconfig.json
├── lib
│ └── extend.js
├── next.config.mjs
├── package-lock.json
└── package.json
├── next15-app-r3f9-react19
├── .dockerignore
├── .gitignore
├── Dockerfile
├── app
│ ├── layout.js
│ └── page.js
├── jsconfig.json
├── lib
│ └── extend.js
├── next.config.mjs
├── package-lock.json
└── package.json
├── next15-pages-r3f9-react19
├── .dockerignore
├── .gitignore
├── Dockerfile
├── jsconfig.json
├── lib
│ └── extend.js
├── next.config.mjs
├── package-lock.json
├── package.json
└── pages
│ ├── _app.js
│ └── index.js
├── sveltekit-threlte8
├── .gitignore
├── .npmrc
├── Dockerfile
├── README.md
├── package-lock.json
├── package.json
├── src
│ ├── app.d.ts
│ ├── app.html
│ ├── components
│ │ └── BackendLogger.svelte
│ ├── lib
│ │ └── index.ts
│ └── routes
│ │ └── +page.svelte
├── static
│ └── favicon.png
├── svelte.config.js
├── tsconfig.json
└── vite.config.ts
├── vite-ts-swc-r3f8-react18
├── .dockerignore
├── .gitignore
├── Dockerfile
├── index.html
├── package-lock.json
├── package.json
├── src
│ ├── App.tsx
│ ├── main.tsx
│ └── vite-env.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
├── vite-ts-swc-r3f9-react19
├── .dockerignore
├── .gitignore
├── Dockerfile
├── index.html
├── package-lock.json
├── package.json
├── src
│ ├── App.tsx
│ ├── extend.ts
│ ├── main.tsx
│ └── vite-env.d.ts
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
├── vite-ts-threlte8
├── .gitignore
├── Dockerfile
├── index.html
├── package-lock.json
├── package.json
├── src
│ ├── App.svelte
│ ├── BackendLogger.svelte
│ ├── main.ts
│ └── vite-env.d.ts
├── svelte.config.js
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts
├── vite-vanilla-js-vertex-alpha
├── .dockerignore
├── .gitignore
├── Dockerfile
├── index.html
├── main.js
├── package-lock.json
├── package.json
└── vite.config.js
└── vite-vanilla-js
├── .dockerignore
├── .gitignore
├── Dockerfile
├── index.html
├── main.js
├── package-lock.json
├── package.json
└── vite.config.js
/.prettierrc:
--------------------------------------------------------------------------------
1 | trailingComma: 'es5'
2 | semi: false
3 | singleQuote: true
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Three.js WebGPU Ecosystem Integration Test Suite
2 |
3 | This is a collection of tests that incrementally add complexity to the setup. Testing is done with Three.js **r173** (2025-01-31). All tests use **WebGPURenderer**, a **TSL** node, and a test of the graphics backend type used. With vanilla [Three.js](https://threejs.org/), [React Three Fiber](https://r3f.docs.pmnd.rs/), and [Threlte](https://threlte.xyz/).
4 |
5 | ## How to test
6 |
7 | Go to a folder, like `next15-pages-vanilla-react19`.
8 |
9 | If you have Docker installed:
10 |
11 | - `npm run docker` to build and run the app in production mode.
12 |
13 | > The Docker image uses Node 20, before `navigator` was added in Node 21.
14 |
15 | Otherwise, to test with your local Node.js version:
16 |
17 | 1. `npm i`
18 | 2. `npm run dev` to check how it works in development.
19 | 3. `npm run start` to check how it works in production.
20 |
21 | ## Results
22 |
23 | A ✅ means the scene renders, and the project works in dev mode, and in production.
24 |
25 | - `next14-app-r3f8-react18`: ✅
26 | - `next14-pages-r3f8-react18`: ✅
27 | - `next15-app-r3f9-react19`: ✅
28 | - `next15-app-r3f9-react19-rsc`: ✅ See [this note](#react-server-components-with-r3f) about RSCs
29 | - `next15-pages-r3f9-react19`: ✅ Unrelated Next.js [HMR warning](#hmr-appisrmanifest-issue)
30 | - `sveltekit-threlte8`: ✅
31 | - `vite-ts-swc-r3f8-react18`: ✅
32 | - `vite-ts-swc-r3f9-react19`: ✅
33 | - `vite-ts-threlte8`: ✅
34 | - `vite-vanilla-js`: ✅
35 |
36 | ### Non-blocking issues
37 |
38 | - ⚠️ Importing a module with top-level await such as `three/examples/jsm/capabilities/WebGPU.js` requires a [Vite config change and causes warnings in Next.js](#top-level-await-issues).
39 |
40 | - ⚠️ WebGPURenderer is initialized with WebGPUBackend before falling back to WebGLBackend. You should [await the init method](#testing-the-backend-type) before checking the backend type or if encounter this [render warning](#render-called-before-backend-initialized-issue).
41 |
42 | ## Top-level Await issues
43 |
44 | Some Three.js modules, like `three/examples/jsm/capabilities/WebGPU`, contain top-level await statements.
45 |
46 | ### Vite
47 |
48 | Importing a module with top-level await will give you this error:
49 |
50 | > ❌ `Top-level await is not available in the configured target environment`
51 |
52 | Add this to your `vite.config.js`:
53 |
54 | ```js
55 | import { defineConfig } from 'vite'
56 |
57 | export default defineConfig({
58 | optimizeDeps: { esbuildOptions: { target: 'esnext' } },
59 | build: { target: 'esnext' },
60 | })
61 | ```
62 |
63 | One of the options fixes development mode, the other fixes production.
64 |
65 | ### Next.js
66 |
67 | Importing a module with top-level await will give you this warning in the browser console and when compiling:
68 |
69 | ```
70 | ./node_modules/three/examples/jsm/capabilities/WebGPU.js
71 | The generated code contains 'async/await' because this module is using "topLevelAwait".
72 | However, your target environment does not appear to support 'async/await'.
73 | As a result, the code may not run as expected or may cause runtime errors.
74 | ```
75 |
76 | ## SSR issues with Next.js and Node.js
77 |
78 | Next.js uses Node.js to Server-Side Render pages on the server. When importing modules on the server, if those modules reference global browser objects like `window`, `document`, `self`, or `navigator` at the top level, you will get a compilation error. _Except_ for `navigator`, which got [added to Node.js 21](https://nodejs.org/en/blog/announcements/v21-release-announce#navigator-object-integration).
79 |
80 | Those top-level references are being tracked down in Three.js for better Next.js support, and this repository is also meant to help testing those issues.
81 |
82 | Generally speaking, as a Next.js developer working with libraries that are meant for browsers like Three.js, it is safer to execute browser-only code inside `useEffect` hooks or similar. See [this article](https://www.joshwcomeau.com/react/the-perils-of-rehydration/).
83 |
84 | ```js
85 | import { browserOnlyFunction } from 'three'
86 |
87 | browserOnlyFunction() // ❌ Don't do that, it runs on the server during SSR
88 |
89 | function MyComponent() {
90 | browserOnlyFunction() // ❌ Don't do that, it runs on the server during SSR
91 |
92 | useEffect(() => {
93 | browserOnlyFunction() // ✅ No problem, runs only in the browser
94 | }, [])
95 |
96 | return // ...
97 | }
98 | ```
99 |
100 | ### ReactCurrentOwner issue
101 |
102 | > ❌ `TypeError: Cannot read properties of undefined (reading 'ReactCurrentOwner')`
103 |
104 | Also a related error during builds:
105 |
106 | > ❌ Cannot read properties of undefined (reading 'ReactCurrentBatchConfig')
107 |
108 | With Next 15, use React Three Fiber v9.
109 |
110 | ### React Server Components with R3F
111 |
112 | You can use React Server Components with R3F. This actually works without `'use client'`:
113 |
114 | ```js
115 |
116 |
117 |
118 |
119 |
120 |
121 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | ```
135 |
136 | `ClientCanvas`, `ClientBox`, and `ClientOrbitControls` are marked with `'use client'`. You can interweave server and client components this way, but expect this approach to be pretty painful.
137 |
138 | ### HMR appIsrManifest issue
139 |
140 | > ⚠️ `[HMR] Invalid message: {"action":"appIsrManifest","data":{}}`
141 |
142 | > `TypeError: Cannot read properties of undefined (reading 'pathname')`
143 |
144 | [Issue on Next.js repo](https://github.com/vercel/next.js/issues/71974).
145 |
146 | **Fixed in `15.1.1-canary.24`.**
147 |
148 | ## Testing the backend type
149 |
150 | WebGPURenderer initially reports WebGPUBackend before falling back to WebGLBackend ([issue](https://github.com/mrdoob/three.js/issues/30024)). There are workarounds for it.
151 |
152 | With vanilla Three.js:
153 |
154 | ```js
155 | renderer = new THREE.WebGPURenderer()
156 | await renderer.init()
157 | console.log(renderer.backend) // WebGPUBackend or WebGLBackend
158 | ```
159 |
160 | With React Three Fiber:
161 |
162 | ```js
163 |