Over the last year I shipped 9 production 3D configurators for polish manufacturers — pools, garage doors, saunas, pergolas, greenhouses, packaging, decorative lamps, terrace roofs, and light-boxes. Each one runs live on its own subdomain of my studio at grodev.pl.
Some of the lessons were obvious in hindsight. Some cost me a weekend of debugging. Sharing the non-obvious ones here.
1. Draco compression is not optional for CAD-heavy models
Manufacturers send you STEP or SolidWorks files exported to glTF. Raw output is 40–120 MB per variant. On 4G mobile that's a 20-second load with an empty white canvas.
Draco compression brings that to 2–5 MB with no visible quality loss on product shots:
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('/draco/') // self-hosted, don't use CDN
const loader = new GLTFLoader()
loader.setDRACOLoader(dracoLoader)
loader.load('/models/pool-3.5m.glb', (gltf) => {
scene.add(gltf.scene)
})
Self-host the decoder — Google's CDN version added ~600 ms to first paint in my measurements. Copy node_modules/three/examples/jsm/libs/draco/ to your public/ folder.
Tooling: gltf-pipeline -i model.glb -o model.draco.glb --draco.compressionLevel 10
2. Instancing beats individual meshes past ~200 objects
A pergola with 40 louvres × 3 tilt positions × user color picker = 120 meshes updating on every frame. Naive approach tanks FPS to 12 on mid-range phones.
InstancedMesh batches identical geometry into one draw call:
const geo = new THREE.BoxGeometry(1, 0.05, 3)
const mat = new THREE.MeshStandardMaterial()
const louvres = new THREE.InstancedMesh(geo, mat, 40)
const dummy = new THREE.Object3D()
for (let i = 0; i < 40; i++) {
dummy.position.set(0, 0, i * 0.15)
dummy.rotation.x = userTilt // update per frame is fine
dummy.updateMatrix()
louvres.setMatrixAt(i, dummy.matrix)
}
louvres.instanceMatrix.needsUpdate = true
scene.add(louvres)
Same for basen tiles, brama slats, sauna wall boards. One material change updates all instances.
3. Ship the price calculator to the server, not the client
Every configurator eventually needs a "Show price" button. Tempting to compute client-side — you already have all the state.
Don't. Manufacturers change prices monthly. Every hardcoded PHP-in-JS multiplication is a redeploy. Also: users open DevTools.
Instead:
// Only what changed goes over the wire
const config = {
model: 'pool-3.5m',
finish: 'granite-grey',
extras: ['lighting', 'cover'],
}
const { price, currency, deliveryWeeks } = await fetch('/api/quote', {
method: 'POST',
body: JSON.stringify(config)
}).then(r => r.json())
Server (Laravel in my case) hits a pricing_rules table with monthly-updated coefficients. Client only knows what a valid config looks like, never the pricing logic.
Bonus: the same endpoint powers the "email me a PDF quote" flow. Zero duplication.
4. Mobile is your target device, not a "nice to have"
70% of my configurator traffic is mobile. Sauna buyers browse on the couch, pool buyers browse at the site. Any deployment that assumes desktop-first will feel broken.
Concrete mobile survival kit:
-
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))— clamping to 2 saves ~40% GPU on retina phones - Detect
WEBGL_lose_contextand reload cleanly when the browser tab is backgrounded on iOS - Preload one hero material, lazy-load the rest — first paint at 400 ms feels instant even if full swatch library is still coming
5. The integration is 60% of the project
Building the WebGL viewer takes 2–3 weeks. Making it actually feed the manufacturer's WooCommerce / CRM / ERP takes another 4–6 weeks — and the client only sees the shiny part.
Budget accordingly. I now quote 12–55k PLN depending on catalog depth and integration surface, not per screen.
Live examples if you want to see any of these techniques in production:
- basen3d.grodev.pl — pool configurator (Draco + PWA)
- pergola3d.grodev.pl — bioclimatic pergola (InstancedMesh louvres)
- lampy3d.grodev.pl — decorative lamp (white-label)
Full studio at grodev.pl — happy to chat if you're integrating similar for a manufacturer.
What tripped you up on your first Three.js production project? Reply — always curious about other people's disaster stories.
Top comments (0)